@oh-my-pi/pi-coding-agent 11.0.3 → 11.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +199 -49
- package/README.md +1 -1
- package/docs/config-usage.md +3 -4
- package/docs/sdk.md +6 -5
- package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
- package/examples/sdk/README.md +1 -1
- package/package.json +19 -11
- package/src/cli/args.ts +11 -94
- package/src/cli/config-cli.ts +1 -1
- package/src/cli/file-processor.ts +3 -3
- package/src/cli/oclif-help.ts +26 -0
- package/src/cli/web-search-cli.ts +148 -0
- package/src/cli.ts +8 -2
- package/src/commands/commit.ts +36 -0
- package/src/commands/config.ts +51 -0
- package/src/commands/grep.ts +41 -0
- package/src/commands/index/index.ts +136 -0
- package/src/commands/jupyter.ts +32 -0
- package/src/commands/plugin.ts +70 -0
- package/src/commands/setup.ts +39 -0
- package/src/commands/shell.ts +29 -0
- package/src/commands/stats.ts +29 -0
- package/src/commands/update.ts +21 -0
- package/src/commands/web-search.ts +50 -0
- package/src/commit/agentic/index.ts +3 -2
- package/src/commit/agentic/tools/analyze-file.ts +1 -3
- package/src/commit/git/errors.ts +4 -6
- package/src/commit/pipeline.ts +3 -2
- package/src/config/keybindings.ts +1 -3
- package/src/config/model-registry.ts +89 -162
- package/src/config/settings-schema.ts +10 -0
- package/src/config.ts +202 -132
- package/src/exa/mcp-client.ts +8 -41
- package/src/export/html/index.ts +1 -1
- package/src/extensibility/extensions/loader.ts +7 -10
- package/src/extensibility/extensions/runner.ts +5 -15
- package/src/extensibility/extensions/types.ts +1 -1
- package/src/extensibility/hooks/runner.ts +6 -9
- package/src/index.ts +0 -1
- package/src/ipy/kernel.ts +10 -22
- package/src/lsp/clients/biome-client.ts +4 -7
- package/src/lsp/clients/lsp-linter-client.ts +4 -6
- package/src/lsp/index.ts +5 -4
- package/src/lsp/utils.ts +18 -0
- package/src/main.ts +86 -181
- package/src/mcp/json-rpc.ts +2 -2
- package/src/mcp/transports/http.ts +12 -49
- package/src/modes/components/armin.ts +1 -3
- package/src/modes/components/assistant-message.ts +4 -4
- package/src/modes/components/bash-execution.ts +5 -3
- package/src/modes/components/branch-summary-message.ts +1 -3
- package/src/modes/components/compaction-summary-message.ts +1 -3
- package/src/modes/components/custom-message.ts +4 -5
- package/src/modes/components/extensions/extension-dashboard.ts +10 -16
- package/src/modes/components/extensions/extension-list.ts +5 -5
- package/src/modes/components/footer.ts +1 -4
- package/src/modes/components/hook-editor.ts +7 -32
- package/src/modes/components/hook-message.ts +4 -5
- package/src/modes/components/model-selector.ts +2 -2
- package/src/modes/components/plugin-settings.ts +16 -20
- package/src/modes/components/python-execution.ts +5 -5
- package/src/modes/components/session-selector.ts +6 -7
- package/src/modes/components/settings-defs.ts +49 -40
- package/src/modes/components/settings-selector.ts +8 -17
- package/src/modes/components/skill-message.ts +1 -3
- package/src/modes/components/status-line-segment-editor.ts +1 -3
- package/src/modes/components/status-line.ts +1 -3
- package/src/modes/components/todo-reminder.ts +5 -7
- package/src/modes/components/tree-selector.ts +10 -12
- package/src/modes/components/ttsr-notification.ts +1 -3
- package/src/modes/components/user-message-selector.ts +2 -4
- package/src/modes/components/welcome.ts +6 -18
- package/src/modes/controllers/event-controller.ts +1 -0
- package/src/modes/controllers/extension-ui-controller.ts +1 -1
- package/src/modes/controllers/input-controller.ts +7 -34
- package/src/modes/controllers/selector-controller.ts +3 -3
- package/src/modes/interactive-mode.ts +27 -1
- package/src/modes/rpc/rpc-client.ts +2 -5
- package/src/modes/rpc/rpc-mode.ts +2 -2
- package/src/modes/theme/theme.ts +2 -6
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +6 -1
- package/src/patch/index.ts +1 -4
- package/src/prompts/agents/explore.md +1 -0
- package/src/prompts/agents/frontmatter.md +2 -1
- package/src/prompts/agents/init.md +1 -0
- package/src/prompts/agents/plan.md +1 -0
- package/src/prompts/agents/reviewer.md +1 -0
- package/src/prompts/system/subagent-submit-reminder.md +2 -0
- package/src/prompts/system/subagent-system-prompt.md +2 -0
- package/src/prompts/system/subagent-user-prompt.md +8 -0
- package/src/prompts/system/system-prompt.md +5 -3
- package/src/prompts/system/web-search.md +6 -4
- package/src/prompts/tools/task.md +216 -163
- package/src/sdk.ts +11 -110
- package/src/session/agent-session.ts +117 -83
- package/src/session/auth-storage.ts +10 -51
- package/src/session/messages.ts +17 -3
- package/src/session/session-manager.ts +30 -30
- package/src/session/streaming-output.ts +1 -1
- package/src/ssh/ssh-executor.ts +6 -3
- package/src/task/agents.ts +2 -0
- package/src/task/discovery.ts +1 -1
- package/src/task/executor.ts +5 -10
- package/src/task/index.ts +43 -23
- package/src/task/render.ts +67 -64
- package/src/task/template.ts +17 -34
- package/src/task/types.ts +49 -22
- package/src/tools/ask.ts +1 -3
- package/src/tools/bash.ts +1 -4
- package/src/tools/browser.ts +5 -7
- package/src/tools/exit-plan-mode.ts +1 -4
- package/src/tools/fetch.ts +1 -3
- package/src/tools/find.ts +4 -3
- package/src/tools/gemini-image.ts +24 -55
- package/src/tools/grep.ts +4 -4
- package/src/tools/index.ts +12 -14
- package/src/tools/notebook.ts +1 -5
- package/src/tools/python.ts +4 -3
- package/src/tools/read.ts +2 -4
- package/src/tools/render-utils.ts +23 -0
- package/src/tools/ssh.ts +8 -12
- package/src/tools/todo-write.ts +1 -4
- package/src/tools/tool-errors.ts +1 -4
- package/src/tools/write.ts +1 -3
- package/src/utils/external-editor.ts +59 -0
- package/src/utils/file-mentions.ts +39 -1
- package/src/utils/image-convert.ts +1 -1
- package/src/utils/image-resize.ts +4 -4
- package/src/web/search/auth.ts +3 -33
- package/src/web/search/index.ts +73 -139
- package/src/web/search/provider.ts +58 -0
- package/src/web/search/providers/anthropic.ts +53 -14
- package/src/web/search/providers/base.ts +22 -0
- package/src/web/search/providers/codex.ts +38 -16
- package/src/web/search/providers/exa.ts +30 -6
- package/src/web/search/providers/gemini.ts +56 -20
- package/src/web/search/providers/jina.ts +28 -5
- package/src/web/search/providers/perplexity.ts +103 -36
- package/src/web/search/render.ts +84 -74
- package/src/web/search/types.ts +285 -59
- package/src/migrations.ts +0 -175
- package/src/session/storage-migration.ts +0 -173
|
@@ -329,6 +329,7 @@ export class AgentSession {
|
|
|
329
329
|
private _streamingEditAbortTriggered = false;
|
|
330
330
|
private _streamingEditCheckedLineCounts = new Map<string, number>();
|
|
331
331
|
private _streamingEditFileCache = new Map<string, string>();
|
|
332
|
+
private _promptInFlight = false;
|
|
332
333
|
|
|
333
334
|
constructor(config: AgentSessionConfig) {
|
|
334
335
|
this.agent = config.agent;
|
|
@@ -693,7 +694,11 @@ export class AgentSession {
|
|
|
693
694
|
.map(line => line.slice(1));
|
|
694
695
|
if (removedLines.length > 0) {
|
|
695
696
|
const resolvedPath = resolveToCwd(path, this.sessionManager.getCwd());
|
|
696
|
-
|
|
697
|
+
let cachedContent = this._streamingEditFileCache.get(resolvedPath);
|
|
698
|
+
if (cachedContent === undefined) {
|
|
699
|
+
this._ensureFileCache(resolvedPath);
|
|
700
|
+
cachedContent = this._streamingEditFileCache.get(resolvedPath);
|
|
701
|
+
}
|
|
697
702
|
if (cachedContent !== undefined) {
|
|
698
703
|
const missing = removedLines.find(line => !cachedContent.includes(normalizeToLF(line)));
|
|
699
704
|
if (missing) {
|
|
@@ -898,7 +903,7 @@ export class AgentSession {
|
|
|
898
903
|
|
|
899
904
|
/** Whether agent is currently streaming a response */
|
|
900
905
|
get isStreaming(): boolean {
|
|
901
|
-
return this.agent.state.isStreaming;
|
|
906
|
+
return this.agent.state.isStreaming || this._promptInFlight;
|
|
902
907
|
}
|
|
903
908
|
|
|
904
909
|
/** Current retry attempt (0 if not retrying) */
|
|
@@ -1014,6 +1019,23 @@ export class AgentSession {
|
|
|
1014
1019
|
this._planReferenceSent = true;
|
|
1015
1020
|
}
|
|
1016
1021
|
|
|
1022
|
+
/**
|
|
1023
|
+
* Inject the plan mode context message into the conversation history.
|
|
1024
|
+
*/
|
|
1025
|
+
async sendPlanModeContext(options?: { deliverAs?: "steer" | "followUp" | "nextTurn" }): Promise<void> {
|
|
1026
|
+
const message = await this._buildPlanModeMessage();
|
|
1027
|
+
if (!message) return;
|
|
1028
|
+
await this.sendCustomMessage(
|
|
1029
|
+
{
|
|
1030
|
+
customType: message.customType,
|
|
1031
|
+
content: message.content,
|
|
1032
|
+
display: message.display,
|
|
1033
|
+
details: message.details,
|
|
1034
|
+
},
|
|
1035
|
+
options ? { deliverAs: options.deliverAs } : undefined,
|
|
1036
|
+
);
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1017
1039
|
resolveRoleModel(role: ModelRole): Model | undefined {
|
|
1018
1040
|
return this._resolveRoleModel(role, this._modelRegistry.getAvailable(), this.model);
|
|
1019
1041
|
}
|
|
@@ -1229,93 +1251,100 @@ export class AgentSession {
|
|
|
1229
1251
|
expandedText: string,
|
|
1230
1252
|
options?: Pick<PromptOptions, "toolChoice" | "images">,
|
|
1231
1253
|
): Promise<void> {
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1254
|
+
this._promptInFlight = true;
|
|
1255
|
+
try {
|
|
1256
|
+
// Flush any pending bash messages before the new prompt
|
|
1257
|
+
this._flushPendingBashMessages();
|
|
1258
|
+
this._flushPendingPythonMessages();
|
|
1235
1259
|
|
|
1236
|
-
|
|
1237
|
-
|
|
1260
|
+
// Reset todo reminder count on new user prompt
|
|
1261
|
+
this._todoReminderCount = 0;
|
|
1238
1262
|
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1263
|
+
// Validate model
|
|
1264
|
+
if (!this.model) {
|
|
1265
|
+
throw new Error(
|
|
1266
|
+
"No model selected.\n\n" +
|
|
1267
|
+
`Use /login, set an API key environment variable, or create ${getAgentDbPath()}\n\n` +
|
|
1268
|
+
"Then use /model to select a model.",
|
|
1269
|
+
);
|
|
1270
|
+
}
|
|
1247
1271
|
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1272
|
+
// Validate API key
|
|
1273
|
+
const apiKey = await this._modelRegistry.getApiKey(this.model, this.sessionId);
|
|
1274
|
+
if (!apiKey) {
|
|
1275
|
+
throw new Error(
|
|
1276
|
+
`No API key found for ${this.model.provider}.\n\n` +
|
|
1277
|
+
`Use /login, set an API key environment variable, or create ${getAgentDbPath()}`,
|
|
1278
|
+
);
|
|
1279
|
+
}
|
|
1256
1280
|
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1281
|
+
// Check if we need to compact before sending (catches aborted responses)
|
|
1282
|
+
const lastAssistant = this._findLastAssistantMessage();
|
|
1283
|
+
if (lastAssistant) {
|
|
1284
|
+
await this._checkCompaction(lastAssistant, false);
|
|
1285
|
+
}
|
|
1262
1286
|
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1287
|
+
// Build messages array (custom messages if any, then user message)
|
|
1288
|
+
const messages: AgentMessage[] = [];
|
|
1289
|
+
const planReferenceMessage = await this._buildPlanReferenceMessage?.();
|
|
1290
|
+
if (planReferenceMessage) {
|
|
1291
|
+
messages.push(planReferenceMessage);
|
|
1292
|
+
}
|
|
1293
|
+
const planModeMessage = await this._buildPlanModeMessage();
|
|
1294
|
+
if (planModeMessage) {
|
|
1295
|
+
messages.push(planModeMessage);
|
|
1296
|
+
}
|
|
1273
1297
|
|
|
1274
|
-
|
|
1298
|
+
messages.push(message);
|
|
1275
1299
|
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1300
|
+
// Inject any pending "nextTurn" messages as context alongside the user message
|
|
1301
|
+
for (const msg of this._pendingNextTurnMessages) {
|
|
1302
|
+
messages.push(msg);
|
|
1303
|
+
}
|
|
1304
|
+
this._pendingNextTurnMessages = [];
|
|
1281
1305
|
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1306
|
+
// Auto-read @filepath mentions
|
|
1307
|
+
const fileMentions = extractFileMentions(expandedText);
|
|
1308
|
+
if (fileMentions.length > 0) {
|
|
1309
|
+
const fileMentionMessages = await generateFileMentionMessages(fileMentions, this.sessionManager.getCwd(), {
|
|
1310
|
+
autoResizeImages: this.settings.get("images.autoResize"),
|
|
1311
|
+
});
|
|
1312
|
+
messages.push(...fileMentionMessages);
|
|
1313
|
+
}
|
|
1288
1314
|
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1315
|
+
// Emit before_agent_start extension event
|
|
1316
|
+
if (this._extensionRunner) {
|
|
1317
|
+
const result = await this._extensionRunner.emitBeforeAgentStart(
|
|
1318
|
+
expandedText,
|
|
1319
|
+
options?.images,
|
|
1320
|
+
this._baseSystemPrompt,
|
|
1321
|
+
);
|
|
1322
|
+
if (result?.messages) {
|
|
1323
|
+
for (const msg of result.messages) {
|
|
1324
|
+
messages.push({
|
|
1325
|
+
role: "custom",
|
|
1326
|
+
customType: msg.customType,
|
|
1327
|
+
content: msg.content,
|
|
1328
|
+
display: msg.display,
|
|
1329
|
+
details: msg.details,
|
|
1330
|
+
timestamp: Date.now(),
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1306
1333
|
}
|
|
1307
|
-
}
|
|
1308
1334
|
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1335
|
+
if (result?.systemPrompt !== undefined) {
|
|
1336
|
+
this.agent.setSystemPrompt(result.systemPrompt);
|
|
1337
|
+
} else {
|
|
1338
|
+
this.agent.setSystemPrompt(this._baseSystemPrompt);
|
|
1339
|
+
}
|
|
1313
1340
|
}
|
|
1314
|
-
}
|
|
1315
1341
|
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1342
|
+
const agentPromptOptions = options?.toolChoice ? { toolChoice: options.toolChoice } : undefined;
|
|
1343
|
+
await this.agent.prompt(messages, agentPromptOptions);
|
|
1344
|
+
await this.waitForRetry();
|
|
1345
|
+
} finally {
|
|
1346
|
+
this._promptInFlight = false;
|
|
1347
|
+
}
|
|
1319
1348
|
}
|
|
1320
1349
|
|
|
1321
1350
|
/**
|
|
@@ -1811,7 +1840,7 @@ export class AgentSession {
|
|
|
1811
1840
|
this.settings.setModelRole(role, `${model.provider}/${model.id}`);
|
|
1812
1841
|
this.settings.getStorage()?.recordModelUsage(`${model.provider}/${model.id}`);
|
|
1813
1842
|
|
|
1814
|
-
// Re-clamp thinking level for new model's capabilities
|
|
1843
|
+
// Re-clamp thinking level for new model's capabilities without persisting settings
|
|
1815
1844
|
this.setThinkingLevel(this.thinkingLevel);
|
|
1816
1845
|
}
|
|
1817
1846
|
|
|
@@ -1830,7 +1859,7 @@ export class AgentSession {
|
|
|
1830
1859
|
this.sessionManager.appendModelChange(`${model.provider}/${model.id}`, "temporary");
|
|
1831
1860
|
this.settings.getStorage()?.recordModelUsage(`${model.provider}/${model.id}`);
|
|
1832
1861
|
|
|
1833
|
-
// Re-clamp thinking level for new model's capabilities
|
|
1862
|
+
// Re-clamp thinking level for new model's capabilities without persisting settings
|
|
1834
1863
|
this.setThinkingLevel(this.thinkingLevel);
|
|
1835
1864
|
}
|
|
1836
1865
|
|
|
@@ -1956,7 +1985,7 @@ export class AgentSession {
|
|
|
1956
1985
|
this.settings.setModelRole("default", `${nextModel.provider}/${nextModel.id}`);
|
|
1957
1986
|
this.settings.getStorage()?.recordModelUsage(`${nextModel.provider}/${nextModel.id}`);
|
|
1958
1987
|
|
|
1959
|
-
// Re-clamp thinking level for new model's capabilities
|
|
1988
|
+
// Re-clamp thinking level for new model's capabilities without persisting settings
|
|
1960
1989
|
this.setThinkingLevel(this.thinkingLevel);
|
|
1961
1990
|
|
|
1962
1991
|
return { model: nextModel, thinkingLevel: this.thinkingLevel, isScoped: false };
|
|
@@ -1978,12 +2007,12 @@ export class AgentSession {
|
|
|
1978
2007
|
* Clamps to model capabilities based on available thinking levels.
|
|
1979
2008
|
* Saves to session, with optional persistence to settings.
|
|
1980
2009
|
*/
|
|
1981
|
-
setThinkingLevel(level: ThinkingLevel,
|
|
2010
|
+
setThinkingLevel(level: ThinkingLevel, persist: boolean = false): void {
|
|
1982
2011
|
const availableLevels = this.getAvailableThinkingLevels();
|
|
1983
2012
|
const effectiveLevel = availableLevels.includes(level) ? level : this._clampThinkingLevel(level, availableLevels);
|
|
1984
2013
|
this.agent.setThinkingLevel(effectiveLevel);
|
|
1985
2014
|
this.sessionManager.appendThinkingLevelChange(effectiveLevel);
|
|
1986
|
-
if (
|
|
2015
|
+
if (persist) {
|
|
1987
2016
|
this.settings.set("defaultThinkingLevel", effectiveLevel);
|
|
1988
2017
|
}
|
|
1989
2018
|
}
|
|
@@ -2000,7 +2029,7 @@ export class AgentSession {
|
|
|
2000
2029
|
const nextIndex = (currentIndex + 1) % levels.length;
|
|
2001
2030
|
const nextLevel = levels[nextIndex];
|
|
2002
2031
|
|
|
2003
|
-
this.setThinkingLevel(nextLevel
|
|
2032
|
+
this.setThinkingLevel(nextLevel);
|
|
2004
2033
|
return nextLevel;
|
|
2005
2034
|
}
|
|
2006
2035
|
|
|
@@ -3941,7 +3970,12 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
3941
3970
|
lines.push("## File Mention\n");
|
|
3942
3971
|
for (const file of fileMsg.files) {
|
|
3943
3972
|
lines.push(`<file path="${file.path}">`);
|
|
3944
|
-
|
|
3973
|
+
if (file.content) {
|
|
3974
|
+
lines.push(file.content);
|
|
3975
|
+
}
|
|
3976
|
+
if (file.image) {
|
|
3977
|
+
lines.push("[Image attached]");
|
|
3978
|
+
}
|
|
3945
3979
|
lines.push("</file>\n");
|
|
3946
3980
|
}
|
|
3947
3981
|
lines.push("\n");
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
* Credential storage for API keys and OAuth tokens.
|
|
3
3
|
* Handles loading, saving, and refreshing credentials from agent.db.
|
|
4
4
|
*/
|
|
5
|
-
import { Buffer } from "node:buffer";
|
|
6
|
-
import * as path from "node:path";
|
|
7
5
|
import {
|
|
8
6
|
antigravityUsageProvider,
|
|
9
7
|
claudeUsageProvider,
|
|
@@ -35,9 +33,8 @@ import {
|
|
|
35
33
|
zaiUsageProvider,
|
|
36
34
|
} from "@oh-my-pi/pi-ai";
|
|
37
35
|
import { logger } from "@oh-my-pi/pi-utils";
|
|
38
|
-
import { getAgentDbPath
|
|
36
|
+
import { getAgentDbPath } from "../config";
|
|
39
37
|
import { AgentStorage } from "./agent-storage";
|
|
40
|
-
import { migrateJsonStorage } from "./storage-migration";
|
|
41
38
|
|
|
42
39
|
export type ApiKeyCredential = {
|
|
43
40
|
type: "api_key";
|
|
@@ -68,7 +65,6 @@ export interface SerializedAuthStorage {
|
|
|
68
65
|
}>
|
|
69
66
|
>;
|
|
70
67
|
runtimeOverrides?: Record<string, string>;
|
|
71
|
-
authPath?: string;
|
|
72
68
|
dbPath?: string;
|
|
73
69
|
}
|
|
74
70
|
|
|
@@ -141,16 +137,13 @@ class AuthStorageUsageCache implements UsageCache {
|
|
|
141
137
|
|
|
142
138
|
/**
|
|
143
139
|
* Credential storage backed by agent.db.
|
|
144
|
-
* Reads from SQLite
|
|
140
|
+
* Reads from SQLite (agent.db).
|
|
145
141
|
*/
|
|
146
142
|
export class AuthStorage {
|
|
147
143
|
private static readonly defaultBackoffMs = 60_000; // Default backoff when no reset time available
|
|
148
144
|
|
|
149
145
|
/** Provider -> credentials cache, populated from agent.db on reload(). */
|
|
150
146
|
private data: Map<string, StoredCredential[]> = new Map();
|
|
151
|
-
private storage: AgentStorage;
|
|
152
|
-
/** Resolved path to agent.db (derived from authPath or used directly if .db). */
|
|
153
|
-
private dbPath: string;
|
|
154
147
|
private runtimeOverrides: Map<string, string> = new Map();
|
|
155
148
|
/** Tracks next credential index per provider:type key for round-robin distribution (non-session use). */
|
|
156
149
|
private providerRoundRobinIndex: Map<string, number> = new Map();
|
|
@@ -166,13 +159,9 @@ export class AuthStorage {
|
|
|
166
159
|
private fallbackResolver?: (provider: string) => string | undefined;
|
|
167
160
|
|
|
168
161
|
private constructor(
|
|
169
|
-
private
|
|
170
|
-
private fallbackPaths: string[] = [],
|
|
171
|
-
storage: AgentStorage,
|
|
162
|
+
private storage: AgentStorage,
|
|
172
163
|
options: AuthStorageOptions = {},
|
|
173
164
|
) {
|
|
174
|
-
this.dbPath = AuthStorage.resolveDbPath(authPath);
|
|
175
|
-
this.storage = storage;
|
|
176
165
|
this.usageProviderResolver = options.usageProviderResolver ?? resolveDefaultUsageProvider;
|
|
177
166
|
this.usageCache = options.usageCache ?? new AuthStorageUsageCache(this.storage);
|
|
178
167
|
this.usageFetch = options.usageFetch ?? fetch;
|
|
@@ -187,17 +176,11 @@ export class AuthStorage {
|
|
|
187
176
|
|
|
188
177
|
/**
|
|
189
178
|
* Create an AuthStorage instance.
|
|
190
|
-
* @param
|
|
191
|
-
* @param fallbackPaths - Additional auth.json paths to migrate (legacy support)
|
|
179
|
+
* @param dbPath - Path to agent.db
|
|
192
180
|
*/
|
|
193
|
-
static async create(
|
|
194
|
-
authPath: string,
|
|
195
|
-
fallbackPaths: string[] = [],
|
|
196
|
-
options: AuthStorageOptions = {},
|
|
197
|
-
): Promise<AuthStorage> {
|
|
198
|
-
const dbPath = AuthStorage.resolveDbPath(authPath);
|
|
181
|
+
static async create(dbPath: string, options: AuthStorageOptions = {}): Promise<AuthStorage> {
|
|
199
182
|
const storage = await AgentStorage.open(dbPath);
|
|
200
|
-
return new AuthStorage(
|
|
183
|
+
return new AuthStorage(storage, options);
|
|
201
184
|
}
|
|
202
185
|
|
|
203
186
|
/**
|
|
@@ -205,14 +188,10 @@ export class AuthStorage {
|
|
|
205
188
|
* Used by subagent workers to bypass discovery and use parent's credentials.
|
|
206
189
|
*/
|
|
207
190
|
static async fromSerialized(data: SerializedAuthStorage, options: AuthStorageOptions = {}): Promise<AuthStorage> {
|
|
208
|
-
const
|
|
209
|
-
const dbPath = data.dbPath ?? AuthStorage.resolveDbPath(authPath);
|
|
191
|
+
const dbPath = data.dbPath ?? getAgentDbPath();
|
|
210
192
|
const storage = await AgentStorage.open(dbPath);
|
|
211
193
|
|
|
212
194
|
const instance = Object.create(AuthStorage.prototype) as AuthStorage;
|
|
213
|
-
instance.authPath = authPath;
|
|
214
|
-
instance.fallbackPaths = [];
|
|
215
|
-
instance.dbPath = dbPath;
|
|
216
195
|
instance.storage = storage;
|
|
217
196
|
instance.data = new Map();
|
|
218
197
|
instance.runtimeOverrides = new Map();
|
|
@@ -271,23 +250,9 @@ export class AuthStorage {
|
|
|
271
250
|
return {
|
|
272
251
|
credentials,
|
|
273
252
|
runtimeOverrides: Object.keys(runtimeOverrides).length > 0 ? runtimeOverrides : undefined,
|
|
274
|
-
authPath: this.authPath,
|
|
275
|
-
dbPath: this.dbPath,
|
|
276
253
|
};
|
|
277
254
|
}
|
|
278
255
|
|
|
279
|
-
/**
|
|
280
|
-
* Converts legacy auth.json path to agent.db path, or returns .db path as-is.
|
|
281
|
-
* @param authPath - Path to auth.json or agent.db
|
|
282
|
-
* @returns Resolved path to agent.db
|
|
283
|
-
*/
|
|
284
|
-
private static resolveDbPath(authPath: string): string {
|
|
285
|
-
if (authPath.endsWith(".db")) {
|
|
286
|
-
return authPath;
|
|
287
|
-
}
|
|
288
|
-
return getAgentDbPath(path.dirname(authPath));
|
|
289
|
-
}
|
|
290
|
-
|
|
291
256
|
/**
|
|
292
257
|
* Set a runtime API key override (not persisted to disk).
|
|
293
258
|
* Used for CLI --api-key flag.
|
|
@@ -313,16 +278,9 @@ export class AuthStorage {
|
|
|
313
278
|
|
|
314
279
|
/**
|
|
315
280
|
* Reload credentials from agent.db.
|
|
316
|
-
*
|
|
281
|
+
* Reloads credentials from the database.
|
|
317
282
|
*/
|
|
318
283
|
async reload(): Promise<void> {
|
|
319
|
-
const agentDir = path.dirname(this.dbPath);
|
|
320
|
-
await migrateJsonStorage({
|
|
321
|
-
agentDir,
|
|
322
|
-
settingsPath: path.join(agentDir, "settings.json"),
|
|
323
|
-
authPaths: [this.authPath, ...this.fallbackPaths],
|
|
324
|
-
});
|
|
325
|
-
|
|
326
284
|
const records = this.storage.listAuthCredentials();
|
|
327
285
|
const grouped = new Map<string, StoredCredential[]>();
|
|
328
286
|
for (const record of records) {
|
|
@@ -388,9 +346,10 @@ export class AuthStorage {
|
|
|
388
346
|
const parts = token.split(".");
|
|
389
347
|
if (parts.length !== 3) return undefined;
|
|
390
348
|
const payloadRaw = parts[1];
|
|
349
|
+
const decoder = new TextDecoder("utf-8");
|
|
391
350
|
try {
|
|
392
351
|
const payload = JSON.parse(
|
|
393
|
-
|
|
352
|
+
decoder.decode(Uint8Array.fromBase64(payloadRaw, { alphabet: "base64url" })),
|
|
394
353
|
) as Record<string, unknown>;
|
|
395
354
|
if (!payload || typeof payload !== "object") return undefined;
|
|
396
355
|
const identifiers: string[] = [];
|
package/src/session/messages.ts
CHANGED
|
@@ -113,7 +113,8 @@ export interface FileMentionMessage {
|
|
|
113
113
|
files: Array<{
|
|
114
114
|
path: string;
|
|
115
115
|
content: string;
|
|
116
|
-
lineCount
|
|
116
|
+
lineCount?: number;
|
|
117
|
+
image?: ImageContent;
|
|
117
118
|
}>;
|
|
118
119
|
timestamp: number;
|
|
119
120
|
}
|
|
@@ -274,10 +275,23 @@ export function convertToLlm(messages: AgentMessage[]): Message[] {
|
|
|
274
275
|
timestamp: m.timestamp,
|
|
275
276
|
};
|
|
276
277
|
case "fileMention": {
|
|
277
|
-
const fileContents = m.files
|
|
278
|
+
const fileContents = m.files
|
|
279
|
+
.map(file => {
|
|
280
|
+
const inner = file.content ? `\n${file.content}\n` : "\n";
|
|
281
|
+
return `<file path="${file.path}">${inner}</file>`;
|
|
282
|
+
})
|
|
283
|
+
.join("\n\n");
|
|
284
|
+
const content: (TextContent | ImageContent)[] = [
|
|
285
|
+
{ type: "text" as const, text: `<system-reminder>\n${fileContents}\n</system-reminder>` },
|
|
286
|
+
];
|
|
287
|
+
for (const file of m.files) {
|
|
288
|
+
if (file.image) {
|
|
289
|
+
content.push(file.image);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
278
292
|
return {
|
|
279
293
|
role: "user",
|
|
280
|
-
content
|
|
294
|
+
content,
|
|
281
295
|
timestamp: m.timestamp,
|
|
282
296
|
};
|
|
283
297
|
}
|
|
@@ -279,26 +279,30 @@ function migrateToCurrentVersion(entries: FileEntry[]): boolean {
|
|
|
279
279
|
return true;
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
-
function parseJsonlEntries<T>(
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
let buffer = content;
|
|
282
|
+
function parseJsonlEntries<T>(buffer: string): T[] {
|
|
283
|
+
let entries: T[] | undefined;
|
|
284
|
+
|
|
286
285
|
while (buffer.length > 0) {
|
|
287
|
-
const
|
|
288
|
-
if (
|
|
289
|
-
|
|
286
|
+
const { values, error, read, done } = Bun.JSONL.parseChunk(buffer);
|
|
287
|
+
if (values.length > 0) {
|
|
288
|
+
const ext = values as T[];
|
|
289
|
+
if (!entries) {
|
|
290
|
+
entries = ext;
|
|
291
|
+
} else {
|
|
292
|
+
entries.push(...ext);
|
|
293
|
+
}
|
|
290
294
|
}
|
|
291
|
-
if (
|
|
292
|
-
const nextNewline = buffer.indexOf("\n",
|
|
295
|
+
if (error) {
|
|
296
|
+
const nextNewline = buffer.indexOf("\n", read);
|
|
293
297
|
if (nextNewline === -1) break;
|
|
294
|
-
buffer = buffer.
|
|
298
|
+
buffer = buffer.substring(nextNewline + 1);
|
|
295
299
|
continue;
|
|
296
300
|
}
|
|
297
|
-
if (
|
|
298
|
-
buffer = buffer.
|
|
299
|
-
if (
|
|
301
|
+
if (read === 0) break;
|
|
302
|
+
buffer = buffer.substring(read);
|
|
303
|
+
if (done) break;
|
|
300
304
|
}
|
|
301
|
-
return entries;
|
|
305
|
+
return entries ?? [];
|
|
302
306
|
}
|
|
303
307
|
|
|
304
308
|
/** Exported for testing */
|
|
@@ -506,17 +510,16 @@ function sanitizeSessionName(value: string | undefined): string | undefined {
|
|
|
506
510
|
}
|
|
507
511
|
|
|
508
512
|
class RecentSessionInfo {
|
|
509
|
-
readonly path: string;
|
|
510
|
-
readonly mtime: number;
|
|
511
|
-
|
|
512
513
|
#fullName: string | undefined;
|
|
513
514
|
#name: string | undefined;
|
|
514
515
|
#timeAgo: string | undefined;
|
|
515
516
|
|
|
516
|
-
constructor(
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
517
|
+
constructor(
|
|
518
|
+
readonly path: string,
|
|
519
|
+
readonly mtime: number,
|
|
520
|
+
header: Record<string, unknown>,
|
|
521
|
+
firstPrompt?: string,
|
|
522
|
+
) {
|
|
520
523
|
// Extract title from session header, falling back to first user prompt, then id
|
|
521
524
|
const trystr = (v: unknown) => (typeof v === "string" ? v : undefined);
|
|
522
525
|
this.#fullName =
|
|
@@ -975,9 +978,6 @@ export class SessionManager {
|
|
|
975
978
|
private sessionId: string = "";
|
|
976
979
|
private sessionName: string | undefined;
|
|
977
980
|
private sessionFile: string | undefined;
|
|
978
|
-
private sessionDir: string;
|
|
979
|
-
private cwd: string;
|
|
980
|
-
private persist: boolean;
|
|
981
981
|
private flushed: boolean = false;
|
|
982
982
|
private fileEntries: FileEntry[] = [];
|
|
983
983
|
private byId: Map<string, SessionEntry> = new Map();
|
|
@@ -989,13 +989,13 @@ export class SessionManager {
|
|
|
989
989
|
private persistChain: Promise<void> = Promise.resolve();
|
|
990
990
|
private persistError: Error | undefined;
|
|
991
991
|
private persistErrorReported = false;
|
|
992
|
-
private storage: SessionStorage;
|
|
993
992
|
|
|
994
|
-
private constructor(
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
993
|
+
private constructor(
|
|
994
|
+
private readonly cwd: string,
|
|
995
|
+
private readonly sessionDir: string,
|
|
996
|
+
private readonly persist: boolean,
|
|
997
|
+
private readonly storage: SessionStorage,
|
|
998
|
+
) {
|
|
999
999
|
if (persist && sessionDir) {
|
|
1000
1000
|
this.storage.ensureDirSync(sessionDir);
|
|
1001
1001
|
}
|
package/src/ssh/ssh-executor.ts
CHANGED
|
@@ -79,6 +79,7 @@ export async function executeSSH(
|
|
|
79
79
|
using child = ptree.spawn(["ssh", ...(await buildRemoteCommand(host, resolvedCommand))], {
|
|
80
80
|
signal: options?.signal,
|
|
81
81
|
timeout: options?.timeout,
|
|
82
|
+
stderr: "full",
|
|
82
83
|
});
|
|
83
84
|
|
|
84
85
|
const sink = new OutputSink({
|
|
@@ -87,9 +88,11 @@ export async function executeSSH(
|
|
|
87
88
|
artifactId: options?.artifactId,
|
|
88
89
|
});
|
|
89
90
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
const streams = [child.stdout.pipeTo(sink.createInput())];
|
|
92
|
+
if (child.stderr) {
|
|
93
|
+
streams.push(child.stderr.pipeTo(sink.createInput()));
|
|
94
|
+
}
|
|
95
|
+
await Promise.allSettled(streams).catch(() => {});
|
|
93
96
|
|
|
94
97
|
try {
|
|
95
98
|
return {
|
package/src/task/agents.ts
CHANGED
|
@@ -48,6 +48,7 @@ const EMBEDDED_AGENT_DEFS: EmbeddedAgentDef[] = [
|
|
|
48
48
|
description: "General-purpose subagent with full capabilities for delegated multi-step tasks",
|
|
49
49
|
spawns: "*",
|
|
50
50
|
model: "default",
|
|
51
|
+
thinkingLevel: "medium",
|
|
51
52
|
},
|
|
52
53
|
template: taskMd,
|
|
53
54
|
},
|
|
@@ -57,6 +58,7 @@ const EMBEDDED_AGENT_DEFS: EmbeddedAgentDef[] = [
|
|
|
57
58
|
name: "quick_task",
|
|
58
59
|
description: "Low-reasoning agent for strictly mechanical updates or data collection only",
|
|
59
60
|
model: "pi/smol",
|
|
61
|
+
thinkingLevel: "minimal",
|
|
60
62
|
},
|
|
61
63
|
template: taskMd,
|
|
62
64
|
},
|
package/src/task/discovery.ts
CHANGED
|
@@ -66,7 +66,7 @@ export async function discoverAgents(cwd: string): Promise<DiscoveryResult> {
|
|
|
66
66
|
}));
|
|
67
67
|
|
|
68
68
|
// Get project directories by walking up from cwd (priority order)
|
|
69
|
-
const projectDirs =
|
|
69
|
+
const projectDirs = findAllNearestProjectConfigDirs("agents", resolvedCwd)
|
|
70
70
|
.filter(entry => agentSources.includes(entry.source))
|
|
71
71
|
.map(entry => ({
|
|
72
72
|
...entry,
|