@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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @poncho-ai/harness@0.59.17 build /home/runner/work/poncho-ai/poncho-ai/packages/harness
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
  CLI Target: es2022
10
10
  ESM Build start
11
11
  ESM dist/isolate-F2PPSUL6.js 53.82 KB
12
- ESM dist/index.js 571.82 KB
13
- ESM ⚡️ Build success in 264ms
12
+ ESM dist/index.js 572.59 KB
13
+ ESM ⚡️ Build success in 249ms
14
14
  DTS Build start
15
- DTS ⚡️ Build success in 7586ms
16
- DTS dist/index.d.ts 104.01 KB
15
+ DTS ⚡️ Build success in 7835ms
16
+ DTS dist/index.d.ts 104.09 KB
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(`Tool activity before interruption:
14784
- ${draft.toolTimeline.join("\n")}`);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poncho-ai/harness",
3
- "version": "0.59.17",
3
+ "version": "0.59.19",
4
4
  "description": "Agent execution runtime - conversation loop, tool dispatch, streaming",
5
5
  "repository": {
6
6
  "type": "git",
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
  : "";
@@ -13,6 +13,7 @@ export {
13
13
  cloneSections,
14
14
  flushTurnDraft,
15
15
  buildToolCompletedText,
16
+ stripPillMetaTokens,
16
17
  recordStandardTurnEvent,
17
18
  buildAssistantMetadata,
18
19
  executeConversationTurn,
@@ -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 = `- done \`${a.tool}\``;
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(`Tool activity before interruption:\n${draft.toolTimeline.join("\n")}`);
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 [
@@ -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
+ });