@poncho-ai/harness 0.59.13 → 0.59.14

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.13 build /home/runner/work/poncho-ai/poncho-ai/packages/harness
2
+ > @poncho-ai/harness@0.59.14 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 561.36 KB
13
- ESM ⚡️ Build success in 214ms
12
+ ESM dist/index.js 564.58 KB
13
+ ESM ⚡️ Build success in 217ms
14
14
  DTS Build start
15
- DTS ⚡️ Build success in 8568ms
15
+ DTS ⚡️ Build success in 8055ms
16
16
  DTS dist/index.d.ts 102.50 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @poncho-ai/harness
2
2
 
3
+ ## 0.59.14
4
+
5
+ ### Patch Changes
6
+
7
+ - [#172](https://github.com/cesr/poncho-ai/pull/172) [`dc61836`](https://github.com/cesr/poncho-ai/commit/dc61836aa99e3cb6e1339dc6f82ffdab522918ba) Thanks [@cesr](https://github.com/cesr)! - Add the `ask_user` built-in tool: the agent can pause the run to ask the
8
+ user a structured, multiple-choice question (the in-app analog of Claude
9
+ Code's AskUserQuestion) instead of asking in plain prose. Each call
10
+ carries 1–4 questions, each with a short header, a `multiSelect` flag,
11
+ and pre-made options; a free-text "Other" escape is rendered by the
12
+ client.
13
+
14
+ The tool is forced to client (`device`) dispatch, so the harness pauses
15
+ the run on a checkpoint carrying the questions and the consumer resumes
16
+ by injecting the user's selections as the tool result — no server-side
17
+ execution (the handler is a defensive stub). The default agent prompt
18
+ now steers the model to reach for `ask_user` whenever it would otherwise
19
+ stop to ask the user to choose between options.
20
+
3
21
  ## 0.59.13
4
22
 
5
23
  ### Patch Changes
package/dist/index.js CHANGED
@@ -710,7 +710,7 @@ Environment: {{runtime.environment}}
710
710
 
711
711
  - Use tools when needed
712
712
  - Explain your reasoning clearly
713
- - Ask clarifying questions when requirements are ambiguous
713
+ - When requirements are ambiguous or you need the user to choose between options, use the \`ask_user\` tool to ask a structured multiple-choice question instead of writing the question as plain text. Reserve plain-text questions for genuinely open-ended asks that have no sensible pre-made options.
714
714
  - Never claim a file/tool change unless the corresponding tool call actually succeeded
715
715
  `;
716
716
  };
@@ -2445,7 +2445,7 @@ var ponchoDocsTool = defineTool({
2445
2445
  import { randomUUID as randomUUID5 } from "crypto";
2446
2446
  import { readFile as readFile9 } from "fs/promises";
2447
2447
  import { resolve as resolve11 } from "path";
2448
- import { defineTool as defineTool12, getTextContent as getTextContent2, createLogger as createLogger7, formatError as fmtErr, url as urlColor } from "@poncho-ai/sdk";
2448
+ import { defineTool as defineTool13, getTextContent as getTextContent2, createLogger as createLogger7, formatError as fmtErr, url as urlColor } from "@poncho-ai/sdk";
2449
2449
 
2450
2450
  // src/upload-store.ts
2451
2451
  import { createHash as createHash2 } from "crypto";
@@ -8591,10 +8591,71 @@ var createSearchTools = () => [
8591
8591
  })
8592
8592
  ];
8593
8593
 
8594
- // src/subagent-tools.ts
8594
+ // src/ask-user-tool.ts
8595
8595
  import { defineTool as defineTool11 } from "@poncho-ai/sdk";
8596
+ var createAskUserTool = () => defineTool11({
8597
+ name: "ask_user",
8598
+ description: "Ask the user one or more structured multiple-choice questions and wait for their answer. Use this INSTEAD of writing a question as plain text whenever you would otherwise pause to let the user choose between options \u2014 clarifying ambiguous requirements, picking an approach, confirming a direction. The user sees tappable option chips and answers with a tap. Prefer this over a prose question: it is faster for the user and gives you a clean answer. Guidelines: ask 1\u20134 questions at once, each with 2\u20134 concrete options; keep `header` very short (a few words); write each option `label` short and its `description` to one line. The user can always type a custom 'Other' answer, so you do not need to add one. Do NOT call any other tool in the same turn as ask_user, and call it at most once per turn. After the user answers you will receive their selections and may continue.",
8599
+ inputSchema: {
8600
+ type: "object",
8601
+ properties: {
8602
+ questions: {
8603
+ type: "array",
8604
+ description: "1\u20134 questions to ask the user at once.",
8605
+ items: {
8606
+ type: "object",
8607
+ properties: {
8608
+ question: {
8609
+ type: "string",
8610
+ description: "The full question text shown to the user."
8611
+ },
8612
+ header: {
8613
+ type: "string",
8614
+ description: "A very short label for this question (a few words, ~12 chars), shown as a chip."
8615
+ },
8616
+ multiSelect: {
8617
+ type: "boolean",
8618
+ description: "If true, the user may select multiple options. Defaults to false (single choice)."
8619
+ },
8620
+ options: {
8621
+ type: "array",
8622
+ description: "The pre-made options. A free-text 'Other' option is added automatically by the client.",
8623
+ items: {
8624
+ type: "object",
8625
+ properties: {
8626
+ label: {
8627
+ type: "string",
8628
+ description: "Short, selectable label for this option."
8629
+ },
8630
+ description: {
8631
+ type: "string",
8632
+ description: "A one-line explanation of what this option means."
8633
+ }
8634
+ },
8635
+ required: ["label"],
8636
+ additionalProperties: false
8637
+ }
8638
+ }
8639
+ },
8640
+ required: ["question", "header", "options"],
8641
+ additionalProperties: false
8642
+ }
8643
+ }
8644
+ },
8645
+ required: ["questions"],
8646
+ additionalProperties: false
8647
+ },
8648
+ handler: async () => {
8649
+ return {
8650
+ error: "ask_user must be answered by the user on the client; it cannot run server-side. This indicates a dispatch misconfiguration."
8651
+ };
8652
+ }
8653
+ });
8654
+
8655
+ // src/subagent-tools.ts
8656
+ import { defineTool as defineTool12 } from "@poncho-ai/sdk";
8596
8657
  var createSubagentTools = (manager) => [
8597
- defineTool11({
8658
+ defineTool12({
8598
8659
  name: "spawn_subagent",
8599
8660
  description: "Spawn a subagent to work on a task in the background. Returns immediately with a subagent ID. The subagent runs independently and its result will be delivered to you as a message in the conversation when it completes.\n\nGuidelines:\n- Spawn all needed subagents in a SINGLE response (they run concurrently), then end your turn with a brief message to the user.\n- Do NOT spawn more subagents in follow-up steps. Wait for results to be delivered before deciding if more work is needed.\n- Prefer doing work yourself for simple or quick tasks. Spawn subagents for substantial, self-contained work.\n- The subagent has no memory of your conversation -- write thorough, self-contained instructions in the task.",
8600
8661
  inputSchema: {
@@ -8629,7 +8690,7 @@ var createSubagentTools = (manager) => [
8629
8690
  return { subagentId, status: "running" };
8630
8691
  }
8631
8692
  }),
8632
- defineTool11({
8693
+ defineTool12({
8633
8694
  name: "message_subagent",
8634
8695
  description: "Send a follow-up message to a completed or stopped subagent. The subagent restarts in the background and its result will be delivered to you as a message when it completes. Only works when the subagent is not currently running.",
8635
8696
  inputSchema: {
@@ -8657,7 +8718,7 @@ var createSubagentTools = (manager) => [
8657
8718
  return { subagentId: id, status: "running" };
8658
8719
  }
8659
8720
  }),
8660
- defineTool11({
8721
+ defineTool12({
8661
8722
  name: "stop_subagent",
8662
8723
  description: "Stop a running subagent. The subagent's conversation is preserved but it will stop processing. Use this to cancel work that is no longer needed.",
8663
8724
  inputSchema: {
@@ -8680,7 +8741,7 @@ var createSubagentTools = (manager) => [
8680
8741
  return { message: `Subagent "${subagentId}" has been stopped.` };
8681
8742
  }
8682
8743
  }),
8683
- defineTool11({
8744
+ defineTool12({
8684
8745
  name: "list_subagents",
8685
8746
  description: "List all subagents that have been spawned in this conversation. Returns each subagent's ID, original task, current status, and message count. Use this to look up subagent IDs before calling message_subagent or stop_subagent.",
8686
8747
  inputSchema: {
@@ -8700,7 +8761,7 @@ var createSubagentTools = (manager) => [
8700
8761
  return { subagents };
8701
8762
  }
8702
8763
  }),
8703
- defineTool11({
8764
+ defineTool12({
8704
8765
  name: "read_subagent",
8705
8766
  description: "Fetch the conversation transcript of a subagent you spawned. Use this to inspect a subagent's intermediate reasoning, tool calls, or full output -- instead of asking it to repeat its work via message_subagent.\n\nModes:\n- 'final' (default): just the last assistant message. Cheap.\n- 'assistant': all assistant messages, no tool calls/results.\n- 'full': every message including tool calls and results. Can be large.\n\nUse since_index / max_messages to page through long transcripts. Only works on subagents directly spawned by this conversation.",
8706
8767
  inputSchema: {
@@ -9578,6 +9639,7 @@ var AgentHarness = class _AgentHarness {
9578
9639
  }
9579
9640
  /** Returns the normalized {access, dispatch} mode for the tool. */
9580
9641
  resolveToolMode(toolName) {
9642
+ if (toolName === "ask_user") return { dispatch: "device" };
9581
9643
  return normalizeToolAccess(this.resolveToolAccess(toolName));
9582
9644
  }
9583
9645
  isToolEnabled(name) {
@@ -9619,6 +9681,9 @@ var AgentHarness = class _AgentHarness {
9619
9681
  this.registerIfMissing(tool);
9620
9682
  }
9621
9683
  }
9684
+ if (this.isToolEnabled("ask_user")) {
9685
+ this.registerIfMissing(createAskUserTool());
9686
+ }
9622
9687
  if (this.environment === "development" && this.isToolEnabled("poncho_docs")) {
9623
9688
  this.registerIfMissing(ponchoDocsTool);
9624
9689
  }
@@ -9627,7 +9692,7 @@ var AgentHarness = class _AgentHarness {
9627
9692
  }
9628
9693
  }
9629
9694
  createGetToolResultByIdTool() {
9630
- return defineTool12({
9695
+ return defineTool13({
9631
9696
  name: "get_tool_result_by_id",
9632
9697
  description: "Retrieve a previously archived full tool result by id for the current conversation. Use this when older tool outputs were truncated in prompt history.",
9633
9698
  inputSchema: {
@@ -14654,7 +14719,7 @@ ${draft.toolTimeline.join("\n")}`);
14654
14719
  };
14655
14720
 
14656
14721
  // src/index.ts
14657
- import { defineTool as defineTool13 } from "@poncho-ai/sdk";
14722
+ import { defineTool as defineTool14 } from "@poncho-ai/sdk";
14658
14723
  export {
14659
14724
  AgentHarness,
14660
14725
  AgentOrchestrator,
@@ -14728,7 +14793,7 @@ export {
14728
14793
  createWriteTool,
14729
14794
  decodeFileInputData,
14730
14795
  defaultAgentDefinition,
14731
- defineTool13 as defineTool,
14796
+ defineTool14 as defineTool,
14732
14797
  deleteOpenAICodexSession,
14733
14798
  deriveUploadKey,
14734
14799
  ensureAgentIdentity,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poncho-ai/harness",
3
- "version": "0.59.13",
3
+ "version": "0.59.14",
4
4
  "description": "Agent execution runtime - conversation loop, tool dispatch, streaming",
5
5
  "repository": {
6
6
  "type": "git",
@@ -0,0 +1,95 @@
1
+ import { defineTool, type ToolDefinition } from "@poncho-ai/sdk";
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // ask_user — pause the run to ask the user a structured, multiple-choice
5
+ // question with pre-made options (the in-app analog of Claude Code's
6
+ // AskUserQuestion). The client renders tappable option chips so the user
7
+ // answers with a tap instead of typing prose.
8
+ //
9
+ // This tool is dispatched to the client ("device" dispatch, forced in
10
+ // AgentHarness.resolveToolMode): the harness pauses the run, emits a
11
+ // checkpoint carrying the questions payload, and the consumer (PonchOS)
12
+ // resumes the run by POSTing the user's selections back as this tool's
13
+ // result. The handler below is a defensive stub — device dispatch
14
+ // intercepts the call before any server-side execution, so it must never
15
+ // actually run.
16
+ // ---------------------------------------------------------------------------
17
+
18
+ export const createAskUserTool = (): ToolDefinition =>
19
+ defineTool({
20
+ name: "ask_user",
21
+ description:
22
+ "Ask the user one or more structured multiple-choice questions and wait for their answer. " +
23
+ "Use this INSTEAD of writing a question as plain text whenever you would otherwise pause to " +
24
+ "let the user choose between options — clarifying ambiguous requirements, picking an approach, " +
25
+ "confirming a direction. The user sees tappable option chips and answers with a tap. " +
26
+ "Prefer this over a prose question: it is faster for the user and gives you a clean answer. " +
27
+ "Guidelines: ask 1–4 questions at once, each with 2–4 concrete options; keep `header` very short " +
28
+ "(a few words); write each option `label` short and its `description` to one line. The user can " +
29
+ "always type a custom 'Other' answer, so you do not need to add one. Do NOT call any other tool " +
30
+ "in the same turn as ask_user, and call it at most once per turn. After the user answers you will " +
31
+ "receive their selections and may continue.",
32
+ inputSchema: {
33
+ type: "object",
34
+ properties: {
35
+ questions: {
36
+ type: "array",
37
+ description: "1–4 questions to ask the user at once.",
38
+ items: {
39
+ type: "object",
40
+ properties: {
41
+ question: {
42
+ type: "string",
43
+ description: "The full question text shown to the user.",
44
+ },
45
+ header: {
46
+ type: "string",
47
+ description:
48
+ "A very short label for this question (a few words, ~12 chars), shown as a chip.",
49
+ },
50
+ multiSelect: {
51
+ type: "boolean",
52
+ description:
53
+ "If true, the user may select multiple options. Defaults to false (single choice).",
54
+ },
55
+ options: {
56
+ type: "array",
57
+ description:
58
+ "The pre-made options. A free-text 'Other' option is added automatically by the client.",
59
+ items: {
60
+ type: "object",
61
+ properties: {
62
+ label: {
63
+ type: "string",
64
+ description: "Short, selectable label for this option.",
65
+ },
66
+ description: {
67
+ type: "string",
68
+ description: "A one-line explanation of what this option means.",
69
+ },
70
+ },
71
+ required: ["label"],
72
+ additionalProperties: false,
73
+ },
74
+ },
75
+ },
76
+ required: ["question", "header", "options"],
77
+ additionalProperties: false,
78
+ },
79
+ },
80
+ },
81
+ required: ["questions"],
82
+ additionalProperties: false,
83
+ },
84
+ handler: async () => {
85
+ // Unreachable in normal operation: ask_user is forced to client/device
86
+ // dispatch, so the harness checkpoints before this handler is invoked.
87
+ // If it ever runs, the tool was misconfigured (dispatch not forced) —
88
+ // surface an error rather than silently resolving with no user input.
89
+ return {
90
+ error:
91
+ "ask_user must be answered by the user on the client; it cannot run server-side. " +
92
+ "This indicates a dispatch misconfiguration.",
93
+ };
94
+ },
95
+ });
@@ -100,7 +100,7 @@ Environment: {{runtime.environment}}
100
100
 
101
101
  - Use tools when needed
102
102
  - Explain your reasoning clearly
103
- - Ask clarifying questions when requirements are ambiguous
103
+ - When requirements are ambiguous or you need the user to choose between options, use the \`ask_user\` tool to ask a structured multiple-choice question instead of writing the question as plain text. Reserve plain-text questions for genuinely open-ended asks that have no sensible pre-made options.
104
104
  - Never claim a file/tool change unless the corresponding tool call actually succeeded
105
105
  `;
106
106
  };
package/src/harness.ts CHANGED
@@ -65,6 +65,7 @@ import { jsonSchemaToZod } from "./schema-converter.js";
65
65
  import type { SkillMetadata } from "./skill-context.js";
66
66
  import { createSkillTools, normalizeScriptPolicyPath } from "./skill-tools.js";
67
67
  import { createSearchTools } from "./search-tools.js";
68
+ import { createAskUserTool } from "./ask-user-tool.js";
68
69
  import { createSubagentTools } from "./subagent-tools.js";
69
70
  import type { SubagentManager } from "./subagent-manager.js";
70
71
  import { trace, context as otelContext, createContextKey, type Context as OtelContextType, SpanStatusCode, SpanKind, diag, DiagConsoleLogger, DiagLogLevel } from "@opentelemetry/api";
@@ -965,6 +966,11 @@ export class AgentHarness {
965
966
 
966
967
  /** Returns the normalized {access, dispatch} mode for the tool. */
967
968
  private resolveToolMode(toolName: string): { access?: "approval"; dispatch?: "device" } {
969
+ // ask_user is always answered by the user on the client. Force device
970
+ // dispatch unconditionally so it pauses the run and checkpoints rather
971
+ // than running its (defensive, error-returning) server-side handler —
972
+ // even if no `poncho.config.js` entry exists for it.
973
+ if (toolName === "ask_user") return { dispatch: "device" };
968
974
  return normalizeToolAccess(this.resolveToolAccess(toolName));
969
975
  }
970
976
 
@@ -1014,6 +1020,9 @@ export class AgentHarness {
1014
1020
  this.registerIfMissing(tool);
1015
1021
  }
1016
1022
  }
1023
+ if (this.isToolEnabled("ask_user")) {
1024
+ this.registerIfMissing(createAskUserTool());
1025
+ }
1017
1026
  if (this.environment === "development" && this.isToolEnabled("poncho_docs")) {
1018
1027
  this.registerIfMissing(ponchoDocsTool);
1019
1028
  }