@poncho-ai/harness 0.59.17 → 0.59.19
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/.turbo/turbo-build.log +5 -5
- package/CHANGELOG.md +27 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +11 -3
- package/package.json +1 -1
- package/src/harness.ts +3 -0
- package/src/orchestrator/index.ts +1 -0
- package/src/orchestrator/orchestrator.ts +2 -1
- package/src/orchestrator/run-conversation-turn.ts +6 -1
- package/src/orchestrator/turn.ts +16 -0
- package/test/pill-tool-call-id.test.ts +56 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @poncho-ai/harness@0.59.
|
|
2
|
+
> @poncho-ai/harness@0.59.19 build /home/runner/work/poncho-ai/poncho-ai/packages/harness
|
|
3
3
|
> node scripts/embed-docs.js && tsup src/index.ts --format esm --dts
|
|
4
4
|
|
|
5
5
|
[embed-docs] Generated poncho-docs.ts with 4 topics
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
[34mCLI[39m Target: es2022
|
|
10
10
|
[34mESM[39m Build start
|
|
11
11
|
[32mESM[39m [1mdist/isolate-F2PPSUL6.js [22m[32m53.82 KB[39m
|
|
12
|
-
[32mESM[39m [1mdist/index.js [22m[
|
|
13
|
-
[32mESM[39m ⚡️ Build success in
|
|
12
|
+
[32mESM[39m [1mdist/index.js [22m[32m572.59 KB[39m
|
|
13
|
+
[32mESM[39m ⚡️ Build success in 249ms
|
|
14
14
|
[34mDTS[39m Build start
|
|
15
|
-
[32mDTS[39m ⚡️ Build success in
|
|
16
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[32m104.
|
|
15
|
+
[32mDTS[39m ⚡️ Build success in 7835ms
|
|
16
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m104.09 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# @poncho-ai/harness
|
|
2
2
|
|
|
3
|
+
## 0.59.19
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#186](https://github.com/cesr/poncho-ai/pull/186) [`b12ccbf`](https://github.com/cesr/poncho-ai/commit/b12ccbf1de63a5ef996b0896898c2afe7238a271) Thanks [@cesr](https://github.com/cesr)! - Stamp the tool-call id onto completed-tool activity lines (`{tcid:<id>}`
|
|
8
|
+
appended after any human detail). Display clients can now join a tool pill to
|
|
9
|
+
its full input/output by id instead of by tool-name + position. The old
|
|
10
|
+
positional/name match misaligns whenever parallel tool calls in a turn
|
|
11
|
+
complete out of declaration order, and can never reach a subagent's
|
|
12
|
+
inner-tool results; id-joining fixes both. The token sits after the first
|
|
13
|
+
`(...)` detail group, so existing clients that only parse inside it are
|
|
14
|
+
unaffected, and it is stripped from model-visible interruption text via the
|
|
15
|
+
new `stripPillMetaTokens` export.
|
|
16
|
+
|
|
17
|
+
## 0.59.18
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- [#184](https://github.com/cesr/poncho-ai/pull/184) [`12ce2be`](https://github.com/cesr/poncho-ai/commit/12ce2be01c9d98b1d9aa634d4d8051c4c0094a44) Thanks [@cesr](https://github.com/cesr)! - Add `browser_download` so the agent can save files from the browser into the
|
|
22
|
+
VFS. The tool fetches a file using the page's logged-in session (so it works
|
|
23
|
+
for files behind a login) and writes the bytes straight to the tenant's VFS via
|
|
24
|
+
`ToolContext.vfs` — never through the model. `url` defaults to the current page,
|
|
25
|
+
or pass a same-origin link's href. The fetch runs inside the page (`evaluate`),
|
|
26
|
+
so it works identically for local and remote/cloud browsers (bytes return over
|
|
27
|
+
CDP). Capped at 25 MB. The harness browser system prompt now documents it under
|
|
28
|
+
a "Saving files" section.
|
|
29
|
+
|
|
3
30
|
## 0.59.17
|
|
4
31
|
|
|
5
32
|
### Patch Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -2135,6 +2135,7 @@ declare const flushTurnDraft: (draft: TurnDraftState) => void;
|
|
|
2135
2135
|
declare const buildToolCompletedText: (event: AgentEvent & {
|
|
2136
2136
|
type: "tool:completed";
|
|
2137
2137
|
}) => string;
|
|
2138
|
+
declare const stripPillMetaTokens: (line: string) => string;
|
|
2138
2139
|
declare const recordStandardTurnEvent: (draft: TurnDraftState, event: AgentEvent) => void;
|
|
2139
2140
|
declare const buildAssistantMetadata: (draft: TurnDraftState, sectionsOverride?: TurnSection[], opts?: {
|
|
2140
2141
|
id?: string;
|
|
@@ -2418,4 +2419,4 @@ type NewEntryNoId = NewConversationEntry extends infer T ? T extends NewConversa
|
|
|
2418
2419
|
*/
|
|
2419
2420
|
declare const appendEntriesSafe: (store: ConversationStore, conversation: Pick<Conversation, "conversationId" | "ownerId" | "tenantId">, entries: NewEntryNoId[], log: Logger) => Promise<ConversationEntry[]>;
|
|
2420
2421
|
|
|
2421
|
-
export { type ActiveConversationRun, type ActiveSubagentRun, type AgentFrontmatter, AgentHarness, type AgentIdentity, type AgentLimitsConfig, type AgentModelConfig, AgentOrchestrator, type ApprovalEventItem, type ArchivedToolResult$1 as ArchivedToolResult, type BashConfig, BashEnvironmentManager, type BashExecutionLimits, type BuiltInToolToggles, CALLBACK_LOCK_STALE_MS, type CallbackStartedEntry, type CompactMessagesOptions, type CompactResult, type CompactionConfig, type ContinuationHooks, type Conversation, type ConversationCreateInit, type ConversationEntry, type ConversationState, type ConversationStatusSnapshot, type ConversationStore, type ConversationSummary, type CreateSkillToolsOptions, type CronJobConfig, DEFAULT_AGENT_DESCRIPTION, DEFAULT_AGENT_NAME, DEFAULT_MAX_STEPS, DEFAULT_MODEL_NAME, DEFAULT_MODEL_PROVIDER, DEFAULT_TAGLINE, DEFAULT_TEMPERATURE, DEFAULT_TIMEOUT, type DefaultAgentDefinitionOptions, type EventSink, type ExecuteTurnResult, type HarnessOptions, type HarnessRunOutput, type HistorySource, InMemoryConversationStore, InMemoryEngine, InMemoryStateStore, type IsolateBinding, type IsolateConfig, LocalMcpBridge, LocalUploadStore, MAX_CONCURRENT_SUBAGENTS, MAX_CONTINUATION_COUNT, MAX_SUBAGENT_CALLBACK_COUNT, MAX_SUBAGENT_NESTING, type MainMemory, type McpConfig, type MemoryConfig, type MemoryStore, type MessagingChannelConfig, type ModelProviderFactory, type MountProvider, type NetworkConfig, type NewConversationEntry, OPENAI_CODEX_CLIENT_ID, type OpenAICodexAuthConfig, type OpenAICodexDeviceAuthRequest, type OpenAICodexSession, type OrchestratorHooks, type OrchestratorOptions, type OtlpConfig, type OtlpOption, PONCHO_UPLOAD_SCHEME, type ParsedAgent, type PendingSubagentApproval, type PendingSubagentResult, type PendingToolCall, type PonchoConfig, PonchoFsAdapter, PostgresEngine, type ProviderConfig, type Recurrence, type RecurrenceType, type Reminder, type ReminderCreateInput, type ReminderStatus, type ReminderStore, type RemoteMcpServerConfig, type RunConversationTurnOpts, type RunConversationTurnResult, type RunOutcome, type RunRequest, type RuntimeRenderContext, S3UploadStore, STALE_SUBAGENT_THRESHOLD_MS, STORAGE_SCHEMA_VERSION, type SecretsStore, type SkillContextEntry, type SkillMetadata, type SkillSource, SqliteEngine, type StateConfig, type StateProviderName, type StateStore, type StorageConfig, type StorageEngine, type StorageFactoryOptions, type StorageProvider, type StoredApproval, type SubagentManager, type SubagentResult, type SubagentResultEntry, type SubagentSpawnResult, type SubagentSummary, type SubagentTranscript, type SubagentTranscriptMode, TOOL_RESULT_ARCHIVE_PARAM, type TelemetryConfig, TelemetryEmitter, type TenantTokenPayload, type ToolAccess, type ToolCall, ToolDispatcher, type ToolExecutionResult, type TurnDraftState, type TurnResultMetadata, type TurnSection, type UploadStore, type UploadsConfig, VFS_SCHEME, VercelBlobUploadStore, type VfsDirEntry, type VfsStat, type VirtualMount, abnormalEndResponse, appendEntriesSafe, applyTurnMetadata, buildAgentDirectoryName, buildApprovalCheckpoints, buildAssistantMetadata, buildSkillContextWindow, buildToolCompletedText, cloneSections, compactMessages, completeOpenAICodexDeviceAuth, computeNextOccurrence, createBashTool, createConversationStore, createConversationStoreFromEngine, createDefaultTools, createDeleteDirectoryTool, createDeleteTool, createEditTool, createMemoryStore, createMemoryStoreFromEngine, createMemoryTools, createModelProvider, createReminderStore, createReminderStoreFromEngine, createReminderTools, createSearchTools, createSecretsStore, createSkillTools, createStateStore, createStorageEngine, createSubagentTools, createTodoStoreFromEngine, createTurnDraftState, createUploadStore, createWriteTool, decodeFileInputData, defaultAgentDefinition, deleteOpenAICodexSession, deriveUploadKey, ensureAgentIdentity, estimateTokens, estimateTotalTokens, executeConversationTurn, findSafeSplitPoint, flushTurnDraft, generateAgentId, getAgentStoreDirectory, getModelContextWindow, getOpenAICodexAccessToken, getOpenAICodexAuthFilePath, getOpenAICodexRequiredScopes, getPendingSubagentResults, getPonchoStoreRoot, isMessageArray, jsonSchemaToZod, lastAssistantText, loadCanonicalHistory, loadPonchoConfig, loadRunHistory, loadSkillContext, loadSkillInstructions, loadSkillMetadata, loadSkillMetadataFromDirs, loadVfsSkillMetadata, mergeSkills, normalizeApprovalCheckpoint, normalizeOtlp, normalizeScriptPolicyPath, normalizeToolAccess, parseAgentFile, parseAgentMarkdown, parseSkillFrontmatter, ponchoDocsTool, readOpenAICodexSession, readSkillResource, realResponseText, recordStandardTurnEvent, renderAgentPrompt, resolveAgentIdentity, resolveCompactionConfig, resolveEnv, resolveMemoryConfig, resolveRunRequest, resolveSkillDirs, resolveStateConfig, runConversationTurn, slugifyStorageComponent, startOpenAICodexDeviceAuth, verifyTenantToken, withToolResultArchiveParam, writeOpenAICodexSession };
|
|
2422
|
+
export { type ActiveConversationRun, type ActiveSubagentRun, type AgentFrontmatter, AgentHarness, type AgentIdentity, type AgentLimitsConfig, type AgentModelConfig, AgentOrchestrator, type ApprovalEventItem, type ArchivedToolResult$1 as ArchivedToolResult, type BashConfig, BashEnvironmentManager, type BashExecutionLimits, type BuiltInToolToggles, CALLBACK_LOCK_STALE_MS, type CallbackStartedEntry, type CompactMessagesOptions, type CompactResult, type CompactionConfig, type ContinuationHooks, type Conversation, type ConversationCreateInit, type ConversationEntry, type ConversationState, type ConversationStatusSnapshot, type ConversationStore, type ConversationSummary, type CreateSkillToolsOptions, type CronJobConfig, DEFAULT_AGENT_DESCRIPTION, DEFAULT_AGENT_NAME, DEFAULT_MAX_STEPS, DEFAULT_MODEL_NAME, DEFAULT_MODEL_PROVIDER, DEFAULT_TAGLINE, DEFAULT_TEMPERATURE, DEFAULT_TIMEOUT, type DefaultAgentDefinitionOptions, type EventSink, type ExecuteTurnResult, type HarnessOptions, type HarnessRunOutput, type HistorySource, InMemoryConversationStore, InMemoryEngine, InMemoryStateStore, type IsolateBinding, type IsolateConfig, LocalMcpBridge, LocalUploadStore, MAX_CONCURRENT_SUBAGENTS, MAX_CONTINUATION_COUNT, MAX_SUBAGENT_CALLBACK_COUNT, MAX_SUBAGENT_NESTING, type MainMemory, type McpConfig, type MemoryConfig, type MemoryStore, type MessagingChannelConfig, type ModelProviderFactory, type MountProvider, type NetworkConfig, type NewConversationEntry, OPENAI_CODEX_CLIENT_ID, type OpenAICodexAuthConfig, type OpenAICodexDeviceAuthRequest, type OpenAICodexSession, type OrchestratorHooks, type OrchestratorOptions, type OtlpConfig, type OtlpOption, PONCHO_UPLOAD_SCHEME, type ParsedAgent, type PendingSubagentApproval, type PendingSubagentResult, type PendingToolCall, type PonchoConfig, PonchoFsAdapter, PostgresEngine, type ProviderConfig, type Recurrence, type RecurrenceType, type Reminder, type ReminderCreateInput, type ReminderStatus, type ReminderStore, type RemoteMcpServerConfig, type RunConversationTurnOpts, type RunConversationTurnResult, type RunOutcome, type RunRequest, type RuntimeRenderContext, S3UploadStore, STALE_SUBAGENT_THRESHOLD_MS, STORAGE_SCHEMA_VERSION, type SecretsStore, type SkillContextEntry, type SkillMetadata, type SkillSource, SqliteEngine, type StateConfig, type StateProviderName, type StateStore, type StorageConfig, type StorageEngine, type StorageFactoryOptions, type StorageProvider, type StoredApproval, type SubagentManager, type SubagentResult, type SubagentResultEntry, type SubagentSpawnResult, type SubagentSummary, type SubagentTranscript, type SubagentTranscriptMode, TOOL_RESULT_ARCHIVE_PARAM, type TelemetryConfig, TelemetryEmitter, type TenantTokenPayload, type ToolAccess, type ToolCall, ToolDispatcher, type ToolExecutionResult, type TurnDraftState, type TurnResultMetadata, type TurnSection, type UploadStore, type UploadsConfig, VFS_SCHEME, VercelBlobUploadStore, type VfsDirEntry, type VfsStat, type VirtualMount, abnormalEndResponse, appendEntriesSafe, applyTurnMetadata, buildAgentDirectoryName, buildApprovalCheckpoints, buildAssistantMetadata, buildSkillContextWindow, buildToolCompletedText, cloneSections, compactMessages, completeOpenAICodexDeviceAuth, computeNextOccurrence, createBashTool, createConversationStore, createConversationStoreFromEngine, createDefaultTools, createDeleteDirectoryTool, createDeleteTool, createEditTool, createMemoryStore, createMemoryStoreFromEngine, createMemoryTools, createModelProvider, createReminderStore, createReminderStoreFromEngine, createReminderTools, createSearchTools, createSecretsStore, createSkillTools, createStateStore, createStorageEngine, createSubagentTools, createTodoStoreFromEngine, createTurnDraftState, createUploadStore, createWriteTool, decodeFileInputData, defaultAgentDefinition, deleteOpenAICodexSession, deriveUploadKey, ensureAgentIdentity, estimateTokens, estimateTotalTokens, executeConversationTurn, findSafeSplitPoint, flushTurnDraft, generateAgentId, getAgentStoreDirectory, getModelContextWindow, getOpenAICodexAccessToken, getOpenAICodexAuthFilePath, getOpenAICodexRequiredScopes, getPendingSubagentResults, getPonchoStoreRoot, isMessageArray, jsonSchemaToZod, lastAssistantText, loadCanonicalHistory, loadPonchoConfig, loadRunHistory, loadSkillContext, loadSkillInstructions, loadSkillMetadata, loadSkillMetadataFromDirs, loadVfsSkillMetadata, mergeSkills, normalizeApprovalCheckpoint, normalizeOtlp, normalizeScriptPolicyPath, normalizeToolAccess, parseAgentFile, parseAgentMarkdown, parseSkillFrontmatter, ponchoDocsTool, readOpenAICodexSession, readSkillResource, realResponseText, recordStandardTurnEvent, renderAgentPrompt, resolveAgentIdentity, resolveCompactionConfig, resolveEnv, resolveMemoryConfig, resolveRunRequest, resolveSkillDirs, resolveStateConfig, runConversationTurn, slugifyStorageComponent, startOpenAICodexDeviceAuth, stripPillMetaTokens, verifyTenantToken, withToolResultArchiveParam, writeOpenAICodexSession };
|
package/dist/index.js
CHANGED
|
@@ -10855,6 +10855,9 @@ Browser sessions (cookies, localStorage, login state) are automatically saved an
|
|
|
10855
10855
|
- Use \`browser_screenshot\` only when you need to see visual layout or images. Screenshots consume significantly more tokens.
|
|
10856
10856
|
- The accessibility tree may be sparse on some pages. If \`browser_snapshot\` returns little or no content, fall back to \`browser_content\` or \`browser_screenshot\`.
|
|
10857
10857
|
|
|
10858
|
+
### Saving files
|
|
10859
|
+
To keep a file the page offers (a PDF, CSV, image, export, etc.), use \`browser_download\` to save it into the user's filesystem. It fetches with the browser's logged-in session, so it works for files behind a login. For a download link, get its href from a snapshot and pass it as \`url\`; for a file that opens in the browser, navigate to it and call \`browser_download\` with no \`url\`. The bytes go straight to the filesystem (not through the chat), so prefer this over screenshotting or copy-pasting file contents.
|
|
10860
|
+
|
|
10858
10861
|
### Tabs and resources
|
|
10859
10862
|
Each conversation gets its own browser tab sharing a single browser instance. Call \`browser_close\` when done to free the tab. If you don't close it, the tab stays open and the user can continue interacting with it.` : "";
|
|
10860
10863
|
const mainMemory = await memoryPromise;
|
|
@@ -12736,8 +12739,10 @@ var buildToolCompletedText = (event) => {
|
|
|
12736
12739
|
if (event.tool === "web_search" && Array.isArray(output?.results)) {
|
|
12737
12740
|
text += ` \u2014 ${output.results.length} result${output.results.length !== 1 ? "s" : ""}`;
|
|
12738
12741
|
}
|
|
12742
|
+
if (event.toolCallId) text += ` {tcid:${event.toolCallId}}`;
|
|
12739
12743
|
return text;
|
|
12740
12744
|
};
|
|
12745
|
+
var stripPillMetaTokens = (line) => line.replace(/\s*\{tcid:[^}]+\}/g, "");
|
|
12741
12746
|
var recordStandardTurnEvent = (draft, event) => {
|
|
12742
12747
|
if (event.type === "model:chunk") {
|
|
12743
12748
|
if (draft.currentTools.length > 0) {
|
|
@@ -13619,7 +13624,7 @@ var AgentOrchestrator = class {
|
|
|
13619
13624
|
for (const a of decidedApprovals) {
|
|
13620
13625
|
if (a.decision === "approved" && a.toolCallId) {
|
|
13621
13626
|
callsToExecute.push({ id: a.toolCallId, name: a.tool, input: a.input });
|
|
13622
|
-
const toolText = `- done \`${a.tool}
|
|
13627
|
+
const toolText = `- done \`${a.tool}\`` + (a.toolCallId ? ` {tcid:${a.toolCallId}}` : "");
|
|
13623
13628
|
draft.toolTimeline.push(toolText);
|
|
13624
13629
|
draft.currentTools.push(toolText);
|
|
13625
13630
|
} else if (a.toolCallId) {
|
|
@@ -14780,8 +14785,10 @@ var runConversationTurn = async (opts) => {
|
|
|
14780
14785
|
const parts = [];
|
|
14781
14786
|
if (draft.assistantResponse.length > 0) parts.push(draft.assistantResponse);
|
|
14782
14787
|
if (draft.toolTimeline.length > 0) {
|
|
14783
|
-
parts.push(
|
|
14784
|
-
|
|
14788
|
+
parts.push(
|
|
14789
|
+
`Tool activity before interruption:
|
|
14790
|
+
${draft.toolTimeline.map(stripPillMetaTokens).join("\n")}`
|
|
14791
|
+
);
|
|
14785
14792
|
}
|
|
14786
14793
|
parts.push(`[This turn was interrupted: ${reason}. The work above may be incomplete.]`);
|
|
14787
14794
|
return [
|
|
@@ -14988,6 +14995,7 @@ export {
|
|
|
14988
14995
|
runConversationTurn,
|
|
14989
14996
|
slugifyStorageComponent,
|
|
14990
14997
|
startOpenAICodexDeviceAuth,
|
|
14998
|
+
stripPillMetaTokens,
|
|
14991
14999
|
verifyTenantToken,
|
|
14992
15000
|
withToolResultArchiveParam,
|
|
14993
15001
|
writeOpenAICodexSession
|
package/package.json
CHANGED
package/src/harness.ts
CHANGED
|
@@ -2376,6 +2376,9 @@ Browser sessions (cookies, localStorage, login state) are automatically saved an
|
|
|
2376
2376
|
- Use \`browser_screenshot\` only when you need to see visual layout or images. Screenshots consume significantly more tokens.
|
|
2377
2377
|
- The accessibility tree may be sparse on some pages. If \`browser_snapshot\` returns little or no content, fall back to \`browser_content\` or \`browser_screenshot\`.
|
|
2378
2378
|
|
|
2379
|
+
### Saving files
|
|
2380
|
+
To keep a file the page offers (a PDF, CSV, image, export, etc.), use \`browser_download\` to save it into the user's filesystem. It fetches with the browser's logged-in session, so it works for files behind a login. For a download link, get its href from a snapshot and pass it as \`url\`; for a file that opens in the browser, navigate to it and call \`browser_download\` with no \`url\`. The bytes go straight to the filesystem (not through the chat), so prefer this over screenshotting or copy-pasting file contents.
|
|
2381
|
+
|
|
2379
2382
|
### Tabs and resources
|
|
2380
2383
|
Each conversation gets its own browser tab sharing a single browser instance. Call \`browser_close\` when done to free the tab. If you don't close it, the tab stays open and the user can continue interacting with it.`
|
|
2381
2384
|
: "";
|
|
@@ -904,7 +904,8 @@ export class AgentOrchestrator {
|
|
|
904
904
|
for (const a of decidedApprovals) {
|
|
905
905
|
if (a.decision === "approved" && a.toolCallId) {
|
|
906
906
|
callsToExecute.push({ id: a.toolCallId, name: a.tool, input: a.input });
|
|
907
|
-
const toolText =
|
|
907
|
+
const toolText =
|
|
908
|
+
`- done \`${a.tool}\`` + (a.toolCallId ? ` {tcid:${a.toolCallId}}` : "");
|
|
908
909
|
draft.toolTimeline.push(toolText);
|
|
909
910
|
draft.currentTools.push(toolText);
|
|
910
911
|
} else if (a.toolCallId) {
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
createTurnDraftState,
|
|
36
36
|
executeConversationTurn,
|
|
37
37
|
flushTurnDraft,
|
|
38
|
+
stripPillMetaTokens,
|
|
38
39
|
} from "./turn.js";
|
|
39
40
|
|
|
40
41
|
const log = createLogger("orchestrator");
|
|
@@ -435,7 +436,11 @@ export const runConversationTurn = async (
|
|
|
435
436
|
const parts: string[] = [];
|
|
436
437
|
if (draft.assistantResponse.length > 0) parts.push(draft.assistantResponse);
|
|
437
438
|
if (draft.toolTimeline.length > 0) {
|
|
438
|
-
parts.push(
|
|
439
|
+
parts.push(
|
|
440
|
+
`Tool activity before interruption:\n${draft.toolTimeline
|
|
441
|
+
.map(stripPillMetaTokens)
|
|
442
|
+
.join("\n")}`,
|
|
443
|
+
);
|
|
439
444
|
}
|
|
440
445
|
parts.push(`[This turn was interrupted: ${reason}. The work above may be incomplete.]`);
|
|
441
446
|
return [
|
package/src/orchestrator/turn.ts
CHANGED
|
@@ -120,9 +120,25 @@ export const buildToolCompletedText = (event: AgentEvent & { type: "tool:complet
|
|
|
120
120
|
text += ` \u2014 ${(output.results as unknown[]).length} result${(output.results as unknown[]).length !== 1 ? "s" : ""}`;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
// Trailing machine token: the tool-call id this pill corresponds to. Lets a
|
|
124
|
+
// display client join the pill to its full input/output by id rather than
|
|
125
|
+
// by tool-name+position (which misaligns whenever parallel tool calls in a
|
|
126
|
+
// turn complete out of declaration order, and can't reach a subagent's
|
|
127
|
+
// inner-tool results at all). Appended AFTER any human detail/parens so
|
|
128
|
+
// older clients \u2014 which only read inside the first `(...)` \u2014 ignore it.
|
|
129
|
+
// Stripped from model-visible interruption text via stripPillMetaTokens.
|
|
130
|
+
if (event.toolCallId) text += ` {tcid:${event.toolCallId}}`;
|
|
131
|
+
|
|
123
132
|
return text;
|
|
124
133
|
};
|
|
125
134
|
|
|
135
|
+
// Remove machine tokens (e.g. `{tcid:\u2026}`) that buildToolCompletedText appends
|
|
136
|
+
// to activity lines for the display client. Use anywhere a tool-timeline line
|
|
137
|
+
// is folded into MODEL-visible text (e.g. interruption reconstruction) so the
|
|
138
|
+
// internal id never leaks into the prompt.
|
|
139
|
+
export const stripPillMetaTokens = (line: string): string =>
|
|
140
|
+
line.replace(/\s*\{tcid:[^}]+\}/g, "");
|
|
141
|
+
|
|
126
142
|
export const recordStandardTurnEvent = (draft: TurnDraftState, event: AgentEvent): void => {
|
|
127
143
|
if (event.type === "model:chunk") {
|
|
128
144
|
if (draft.currentTools.length > 0) {
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import type { AgentEvent } from "@poncho-ai/sdk";
|
|
3
|
+
import { buildToolCompletedText, stripPillMetaTokens } from "../src/orchestrator/turn.js";
|
|
4
|
+
|
|
5
|
+
const completed = (over: Partial<AgentEvent & { type: "tool:completed" }>) =>
|
|
6
|
+
buildToolCompletedText({
|
|
7
|
+
type: "tool:completed",
|
|
8
|
+
tool: "bash",
|
|
9
|
+
toolCallId: "toolu_abc123",
|
|
10
|
+
input: { command: "ls -la" },
|
|
11
|
+
output: {},
|
|
12
|
+
duration: 45,
|
|
13
|
+
outputTokenEstimate: 0,
|
|
14
|
+
...over,
|
|
15
|
+
} as AgentEvent & { type: "tool:completed" });
|
|
16
|
+
|
|
17
|
+
describe("buildToolCompletedText tcid token", () => {
|
|
18
|
+
it("appends the tool-call id after the human detail", () => {
|
|
19
|
+
const line = completed({});
|
|
20
|
+
expect(line).toContain("- done `bash`");
|
|
21
|
+
expect(line).toContain("(45ms, ls -la)");
|
|
22
|
+
expect(line.endsWith("{tcid:toolu_abc123}")).toBe(true);
|
|
23
|
+
// Token sits AFTER the first (...) detail group so old clients ignore it.
|
|
24
|
+
expect(line.indexOf("{tcid:")).toBeGreaterThan(line.indexOf(")"));
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("omits the token when there is no tool-call id", () => {
|
|
28
|
+
const line = completed({ toolCallId: undefined as unknown as string });
|
|
29
|
+
expect(line).not.toContain("{tcid:");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("keeps the id intact alongside the subagent token", () => {
|
|
33
|
+
const line = completed({
|
|
34
|
+
tool: "spawn_subagent",
|
|
35
|
+
toolCallId: "toolu_xyz",
|
|
36
|
+
input: { task: "research" },
|
|
37
|
+
output: { subagentId: "conv_child" },
|
|
38
|
+
});
|
|
39
|
+
expect(line).toContain("[subagent:conv_child]");
|
|
40
|
+
expect(line).toContain("{tcid:toolu_xyz}");
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe("stripPillMetaTokens", () => {
|
|
45
|
+
it("removes the tcid token (and its leading space) for model-visible text", () => {
|
|
46
|
+
expect(stripPillMetaTokens("- done `bash` (45ms, ls) {tcid:toolu_abc}")).toBe(
|
|
47
|
+
"- done `bash` (45ms, ls)",
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("is a no-op for lines without a token", () => {
|
|
52
|
+
expect(stripPillMetaTokens("- start `web_search` (\"q\")")).toBe(
|
|
53
|
+
"- start `web_search` (\"q\")",
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
});
|