Skip to content

Commit 28cdd44

Browse files
authored
Merge pull request #27 from plotday/fix/tool-not-found
plot agent logs
2 parents f0cd57d + 8030c59 commit 28cdd44

11 files changed

Lines changed: 316 additions & 168 deletions

File tree

.changeset/heavy-steaks-fall.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@plotday/tool-outlook-calendar": patch
3+
"@plotday/tool-google-calendar": patch
4+
---
5+
6+
Changed: Remove defunct static tool id

.changeset/silent-otters-follow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@plotday/sdk": patch
3+
---
4+
5+
Fixed: Improper use of tools in Agent and Tool base classes causing "Tool not found" errors

.changeset/wise-baths-smell.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@plotday/sdk": minor
3+
---
4+
5+
Added: plot agent logs

sdk/cli/commands/agent-logs.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import * as fs from "fs";
2+
3+
import * as out from "../utils/output";
4+
import { getGlobalTokenPath } from "../utils/token";
5+
import { handleSSEStream } from "../utils/sse";
6+
7+
interface AgentLogsOptions {
8+
agentId: string;
9+
environment?: string;
10+
deployToken?: string;
11+
apiUrl: string;
12+
}
13+
14+
/**
15+
* Stream agent logs in real-time
16+
*/
17+
export async function agentLogsCommand(options: AgentLogsOptions) {
18+
const { agentId, environment = "personal", apiUrl } = options;
19+
20+
// Load deploy token
21+
let deployToken = options.deployToken;
22+
23+
if (!deployToken) {
24+
// Try to load from PLOT_DEPLOY_TOKEN environment variable
25+
deployToken = process.env.PLOT_DEPLOY_TOKEN;
26+
}
27+
28+
if (!deployToken) {
29+
// Try to load from global token file
30+
const globalTokenPath = getGlobalTokenPath();
31+
if (fs.existsSync(globalTokenPath)) {
32+
try {
33+
deployToken = fs.readFileSync(globalTokenPath, "utf-8").trim();
34+
} catch (error) {
35+
console.warn(
36+
`Warning: Failed to read global token file: ${globalTokenPath}`
37+
);
38+
}
39+
}
40+
}
41+
42+
if (!deployToken) {
43+
out.error(
44+
"Authentication required",
45+
"Run 'plot login' or provide token via --deploy-token or PLOT_DEPLOY_TOKEN env var"
46+
);
47+
process.exit(1);
48+
}
49+
50+
// Construct API URL
51+
const url = new URL(`/v1/agent/${agentId}/logs`, apiUrl);
52+
if (environment !== "personal") {
53+
url.searchParams.set("environment", environment);
54+
}
55+
56+
out.info(`Streaming logs for agent ${agentId}`, [
57+
`Environment: ${environment}`,
58+
"Press Ctrl+C to stop",
59+
]);
60+
out.blank();
61+
62+
try {
63+
const response = await fetch(url.toString(), {
64+
method: "GET",
65+
headers: {
66+
Accept: "text/event-stream",
67+
Authorization: `Bearer ${deployToken}`,
68+
},
69+
});
70+
71+
if (!response.ok) {
72+
const errorText = await response.text();
73+
out.error(
74+
`Failed to connect: ${response.status} ${response.statusText}`,
75+
errorText
76+
);
77+
process.exit(1);
78+
}
79+
80+
// Handle SSE stream with custom log formatting
81+
await handleSSEStream(response, {
82+
onProgress: (message) => {
83+
// Initial connection message
84+
out.plain(message);
85+
},
86+
onEvent: (event, data) => {
87+
if (event === "log") {
88+
// Format log entry
89+
const { timestamp, severity, message } = data;
90+
const time = new Date(timestamp).toLocaleTimeString();
91+
92+
// Color-code by severity
93+
let severityColor = "";
94+
let severityLabel = severity.toUpperCase().padEnd(5);
95+
96+
switch (severity) {
97+
case "error":
98+
severityColor = "\x1b[31m"; // Red
99+
break;
100+
case "warn":
101+
severityColor = "\x1b[33m"; // Yellow
102+
break;
103+
case "info":
104+
severityColor = "\x1b[36m"; // Cyan
105+
break;
106+
default:
107+
severityColor = "\x1b[37m"; // White
108+
}
109+
110+
const reset = "\x1b[0m";
111+
const gray = "\x1b[90m";
112+
113+
console.log(
114+
`${gray}[${time}]${reset} ${severityColor}${severityLabel}${reset} ${message}`
115+
);
116+
}
117+
},
118+
onError: (error) => {
119+
out.error("Stream error", error);
120+
},
121+
});
122+
} catch (error) {
123+
out.error("Connection failed", String(error));
124+
process.exit(1);
125+
}
126+
}

sdk/cli/commands/generate.ts

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import prompts from "prompts";
66

77
import * as out from "../utils/output";
88
import { detectPackageManager } from "../utils/packageManager";
9-
import { getGlobalTokenPath } from "../utils/token";
109
import { handleSSEStream } from "../utils/sse";
10+
import { getGlobalTokenPath } from "../utils/token";
1111

1212
interface GenerateOptions {
1313
dir: string;
@@ -311,32 +311,38 @@ export async function generateCommand(options: GenerateOptions) {
311311

312312
// Update @plotday/sdk to latest and install packages
313313
try {
314-
out.progress("Updating @plotday/sdk to latest version...");
314+
out.progress("Updating SDK to latest version...");
315315

316316
const updateCommand =
317-
packageManager === "npm" ? "npm install @plotday/sdk@latest" :
318-
packageManager === "pnpm" ? "pnpm add @plotday/sdk@latest" :
319-
"yarn add @plotday/sdk@latest";
317+
packageManager === "npm"
318+
? "npm install @plotday/sdk@latest"
319+
: packageManager === "pnpm"
320+
? "pnpm add @plotday/sdk@latest"
321+
: "yarn add @plotday/sdk@latest";
320322

321323
execSync(updateCommand, { cwd: agentPath, stdio: "ignore" });
322324

323325
out.progress("Installing dependencies...");
324326

325327
const installCommand =
326-
packageManager === "yarn" ? "yarn" :
327-
`${packageManager} install`;
328+
packageManager === "yarn" ? "yarn" : `${packageManager} install`;
328329

329330
execSync(installCommand, { cwd: agentPath, stdio: "ignore" });
330331

331-
out.success("Dependencies installed successfully!");
332+
out.success("Dependencies installed.");
332333
} catch (error) {
333-
out.warning(
334-
"Couldn't install dependencies",
335-
[
336-
`Run '${packageManager === "npm" ? "npm install @plotday/sdk@latest" : packageManager === "pnpm" ? "pnpm add @plotday/sdk@latest" : "yarn add @plotday/sdk@latest"}' in ${options.dir}`,
337-
`Then run '${packageManager === "yarn" ? "yarn" : `${packageManager} install`}'`
338-
]
339-
);
334+
out.warning("Couldn't install dependencies", [
335+
`Run '${
336+
packageManager === "npm"
337+
? "npm install @plotday/sdk@latest"
338+
: packageManager === "pnpm"
339+
? "pnpm add @plotday/sdk@latest"
340+
: "yarn add @plotday/sdk@latest"
341+
}' in ${options.dir}`,
342+
`Then run '${
343+
packageManager === "yarn" ? "yarn" : `${packageManager} install`
344+
}'`,
345+
]);
340346
}
341347

342348
out.blank();

sdk/cli/index.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Command, Option } from "commander";
33
import { readFileSync } from "fs";
44
import { join } from "path";
55

6+
import { agentLogsCommand } from "./commands/agent-logs";
67
import { buildCommand } from "./commands/build";
78
import { createCommand } from "./commands/create";
89
import { deployCommand } from "./commands/deploy";
@@ -116,6 +117,29 @@ agent
116117
return deployCommand(opts);
117118
});
118119

120+
agent
121+
.command("logs <agent-id>")
122+
.description("Stream real-time logs from an agent")
123+
.option(
124+
"-e, --environment <env>",
125+
"Agent environment (personal, private, review)",
126+
"personal"
127+
)
128+
.option("--deploy-token <token>", "Authentication token")
129+
.action(function (this: Command, agentId: string) {
130+
const opts = this.optsWithGlobals() as {
131+
environment?: string;
132+
deployToken?: string;
133+
apiUrl: string;
134+
};
135+
return agentLogsCommand({
136+
agentId,
137+
environment: opts.environment,
138+
deployToken: opts.deployToken,
139+
apiUrl: opts.apiUrl,
140+
});
141+
});
142+
119143
// Priority subcommand group
120144
const priority = program
121145
.command("priority")

sdk/cli/templates/AGENTS.template.md

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -374,16 +374,15 @@ try {
374374

375375
## Common Pitfalls
376376

377-
1. **Don't use instance variables for state** - Anything stored in memory is lost after function execution. Always use the Store tool for data that needs to persist.
378-
2. **Don't forget timeout limits** - Each execution has ~10 seconds. Break long operations into batches with the Run tool.
379-
3. **Don't assume execution order** - Batches may run on different workers. Store all necessary state between executions.
380-
4. **Always use Callback tool for persistent references** - Direct function references don't survive worker restarts.
381-
5. **Store auth tokens** - Don't re-request authentication unnecessarily.
382-
6. **Clean up callbacks and stored state** - Delete callbacks and Store entries when no longer needed.
383-
7. **Handle missing auth gracefully** - Check for stored auth before operations.
384-
8. **Batch size matters** - Process enough items per batch to be efficient, but few enough to stay under time limits.
385-
9. **Processing self-created activities** - Other users may change an Activity created by the agent, resulting in an \`activity\` call. Be sure to check the \`changes === null\` and/or \`activity.author.id !== this.id\` to avoid re-processing.
386-
10. Activity with type ActivityType.Note typically do not have a start or end set, unless they're a note about a specific day or time that shouldn't be shown until then.
377+
- **Don't use instance variables for state** - Anything stored in memory is lost after function execution. Always use the Store tool for data that needs to persist.
378+
- **Processing self-created activities** - Other users may change an Activity created by the agent, resulting in an \`activity\` call. Be sure to check the \`changes === null\` and/or \`activity.author.id !== this.id\` to avoid re-processing.
379+
- Activity with type ActivityType.Note typically do not have a start or end set, unless they're a note about a specific day or time that shouldn't be shown until then.
380+
- Don't add the Tools instance as an instance variable. Any tools needed must bet rieved via \`this.tools.get(ToolClass)\` in the constructor and assigned to instance variables.
381+
- **Don't forget runtime limits** - Each execution has ~10 seconds. Break long operations into batches with the Run tool. Process enough items per batch to be efficient, but few enough to stay under time limits.
382+
- **Always use Callback tool for persistent references** - Direct function references don't survive worker restarts.
383+
- **Store auth tokens** - Don't re-request authentication unnecessarily.
384+
- **Clean up callbacks and stored state** - Delete callbacks and Store entries when no longer needed.
385+
- **Handle missing auth gracefully** - Check for stored auth before operations.
387386

388387
## Type Patterns
389388

sdk/cli/utils/sse.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export interface SSEHandlers {
1212
onProgress?: (message: string) => void;
1313
onResult?: (data: any) => void;
1414
onError?: (error: string) => void;
15+
onEvent?: (event: string, data: any) => void;
1516
}
1617

1718
/**
@@ -73,6 +74,11 @@ export async function handleSSEStream(
7374
case "error":
7475
handlers.onError?.(parsedData.error);
7576
throw new Error(parsedData.error);
77+
default:
78+
// Handle custom events via onEvent handler
79+
if (handlers.onEvent) {
80+
handlers.onEvent(currentEvent.event, parsedData);
81+
}
7682
}
7783
}
7884

0 commit comments

Comments
 (0)