@poncho-ai/cli 0.40.2 → 0.40.4

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/cli@0.40.2 build /home/runner/work/poncho-ai/poncho-ai/packages/cli
2
+ > @poncho-ai/cli@0.40.4 build /home/runner/work/poncho-ai/poncho-ai/packages/cli
3
3
  > tsup src/index.ts src/cli.ts --format esm --dts
4
4
 
5
5
  CLI Building entry: src/cli.ts, src/index.ts
@@ -9,10 +9,10 @@
9
9
  ESM Build start
10
10
  ESM dist/cli.js 528.00 B
11
11
  ESM dist/index.js 3.10 KB
12
- ESM dist/run-interactive-ink-LJTKUUV4.js 23.38 KB
13
- ESM dist/chunk-KVGMTYDD.js 674.92 KB
14
- ESM ⚡️ Build success in 83ms
12
+ ESM dist/run-interactive-ink-3BKXJ5SX.js 23.38 KB
13
+ ESM dist/chunk-4XGZI7KU.js 664.72 KB
14
+ ESM ⚡️ Build success in 75ms
15
15
  DTS Build start
16
- DTS ⚡️ Build success in 4702ms
16
+ DTS ⚡️ Build success in 4000ms
17
17
  DTS dist/cli.d.ts 20.00 B
18
- DTS dist/index.d.ts 13.25 KB
18
+ DTS dist/index.d.ts 13.56 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,96 @@
1
1
  # @poncho-ai/cli
2
2
 
3
+ ## 0.40.4
4
+
5
+ ### Patch Changes
6
+
7
+ - [`ff89631`](https://github.com/cesr/poncho-ai/commit/ff89631715e54d6fdce174943e6e0fc9e4ce5d1e) Thanks [@cesr](https://github.com/cesr)! - harness: export `defaultAgentDefinition` so SDK consumers can match `poncho init` exactly
8
+
9
+ Lifts the `AGENT_TEMPLATE` markdown body from `@poncho-ai/cli` (where it lived
10
+ inside the `init` scaffolding) into a public helper on `@poncho-ai/harness`.
11
+ SDK consumers (PonchOS, custom servers, anyone calling
12
+ `new AgentHarness({ agentDefinition })` directly) can now do:
13
+
14
+ ```ts
15
+ import { defaultAgentDefinition } from "@poncho-ai/harness";
16
+
17
+ const harness = new AgentHarness({
18
+ agentDefinition: defaultAgentDefinition({
19
+ name: "poncho",
20
+ modelName: "claude-sonnet-4-6",
21
+ }),
22
+ // ... storageEngine, config, etc.
23
+ });
24
+ ```
25
+
26
+ This eliminates hand-copying the template — drift between consumers and
27
+ `poncho init` is no longer possible.
28
+
29
+ The CLI's `AGENT_TEMPLATE` export is preserved as a thin back-compat
30
+ wrapper that delegates to `defaultAgentDefinition`. No behavior change.
31
+
32
+ API additions (harness):
33
+ - `defaultAgentDefinition(opts?: DefaultAgentDefinitionOptions): string`
34
+ - `DefaultAgentDefinitionOptions`
35
+ - `DEFAULT_AGENT_NAME`, `DEFAULT_AGENT_DESCRIPTION`,
36
+ `DEFAULT_MODEL_PROVIDER`, `DEFAULT_MODEL_NAME`, `DEFAULT_TEMPERATURE`,
37
+ `DEFAULT_MAX_STEPS`, `DEFAULT_TIMEOUT` constants
38
+
39
+ - Updated dependencies [[`ff89631`](https://github.com/cesr/poncho-ai/commit/ff89631715e54d6fdce174943e6e0fc9e4ce5d1e)]:
40
+ - @poncho-ai/harness@0.43.0
41
+
42
+ ## 0.40.3
43
+
44
+ ### Patch Changes
45
+
46
+ - [`111d24e`](https://github.com/cesr/poncho-ai/commit/111d24efaab054ef7543c396085f8f4d41e7976a) Thanks [@cesr](https://github.com/cesr)! - cli: include VFS skills in the chat input slash command menu
47
+
48
+ The `/api/slash-commands` endpoint was returning only repo-loaded skills,
49
+ so tenant-authored skills stored in the VFS (`/skills/<name>/SKILL.md`)
50
+ never appeared in the `/` autocomplete bar even though the agent could
51
+ already see and run them at conversation time.
52
+
53
+ The endpoint now resolves skills per-tenant via a new
54
+ `harness.listSkillsForTenant(tenantId)` and applies the same repo-wins
55
+ collision semantics used elsewhere in the harness.
56
+
57
+ - [`39793b0`](https://github.com/cesr/poncho-ai/commit/39793b0ab11ed26f140af6fc9c0cd3e1b1c83fec) Thanks [@cesr](https://github.com/cesr)! - harness: extract `runConversationTurn` helper; refactor CLI to use it
58
+
59
+ Lifts the inline turn lifecycle from the CLI's
60
+ `POST /api/conversations/:id/messages` handler (~280 lines of orchestration)
61
+ into a new public helper at `@poncho-ai/harness`.
62
+
63
+ The helper handles the full conversation lifecycle for a primary chat
64
+ turn: load the conversation with archive, resolve canonical history,
65
+ upload files via the harness's upload store, build stable user/assistant
66
+ ids, persist the user message immediately, drive `executeConversationTurn`,
67
+ periodically persist the in-flight assistant draft on `step:completed`
68
+ and `tool:approval:required`, persist on `tool:approval:checkpoint` and
69
+ `run:completed` continuation, rebuild history on `compaction:completed`,
70
+ apply turn metadata on success, and persist partial state on
71
+ cancel/error.
72
+
73
+ Caller responsibilities (auth, active-run dedup, streaming, continuation
74
+ HTTP self-fetch, title inference) stay outside the helper — passed in
75
+ via opts or handled around the call. `opts.onEvent` is invoked for every
76
+ `AgentEvent` for downstream forwarding (SSE, WebSocket, telemetry, etc.).
77
+
78
+ The CLI's handler now delegates to `runConversationTurn` (drops from
79
+ ~430 to ~150 lines). Consumers like PonchOS can call the same helper
80
+ to ship the _exact_ same conversation lifecycle without duplicating
81
+ the orchestration.
82
+
83
+ Public API additions:
84
+ - `runConversationTurn(opts): Promise<RunConversationTurnResult>`
85
+ - `RunConversationTurnOpts`
86
+ - `RunConversationTurnResult`
87
+
88
+ No behavior changes. The helper is a verbatim extraction of the CLI's
89
+ prior inline implementation.
90
+
91
+ - Updated dependencies [[`111d24e`](https://github.com/cesr/poncho-ai/commit/111d24efaab054ef7543c396085f8f4d41e7976a), [`39793b0`](https://github.com/cesr/poncho-ai/commit/39793b0ab11ed26f140af6fc9c0cd3e1b1c83fec)]:
92
+ - @poncho-ai/harness@0.42.0
93
+
3
94
  ## 0.40.2
4
95
 
5
96
  ### Patch Changes
@@ -14,7 +14,6 @@ import {
14
14
  createConversationStore as createConversationStore2,
15
15
  createConversationStoreFromEngine as createConversationStoreFromEngine2,
16
16
  createUploadStore as createUploadStore2,
17
- deriveUploadKey,
18
17
  ensureAgentIdentity as ensureAgentIdentity3,
19
18
  loadPonchoConfig as loadPonchoConfig4,
20
19
  parseAgentMarkdown as parseAgentMarkdown2,
@@ -32,6 +31,7 @@ import {
32
31
  normalizeApprovalCheckpoint,
33
32
  buildApprovalCheckpoints,
34
33
  applyTurnMetadata,
34
+ runConversationTurn,
35
35
  withToolResultArchiveParam,
36
36
  AgentOrchestrator
37
37
  } from "@poncho-ai/harness";
@@ -571,7 +571,7 @@ var WEB_UI_STYLES = `
571
571
  .sidebar-segmented {
572
572
  display: inline-flex;
573
573
  align-self: stretch;
574
- margin: 12px 6px 0;
574
+ margin: 12px 6px 8px;
575
575
  padding: 3px;
576
576
  background: var(--surface-3);
577
577
  border-radius: 999px;
@@ -12028,6 +12028,7 @@ import { readFile as readFile4 } from "fs/promises";
12028
12028
  import { existsSync } from "fs";
12029
12029
  import { dirname as dirname4, relative, resolve as resolve3 } from "path";
12030
12030
  import { fileURLToPath } from "url";
12031
+ import { defaultAgentDefinition } from "@poncho-ai/harness";
12031
12032
  var __dirname = dirname4(fileURLToPath(import.meta.url));
12032
12033
  var packageRoot = resolve3(__dirname, "..");
12033
12034
  var readCliVersion = async () => {
@@ -12043,33 +12044,12 @@ var readCliVersion = async () => {
12043
12044
  }
12044
12045
  return fallback;
12045
12046
  };
12046
- var AGENT_TEMPLATE = (name, id, options) => `---
12047
- name: ${name}
12048
- id: ${id}
12049
- description: A helpful Poncho assistant
12050
- model:
12051
- provider: ${options.modelProvider}
12052
- name: ${options.modelName}
12053
- temperature: 0.2
12054
- limits:
12055
- maxSteps: 20
12056
- timeout: 300
12057
- ---
12058
-
12059
- # {{name}}
12060
-
12061
- You are **{{name}}**, a helpful assistant built with Poncho.
12062
-
12063
- Working directory: {{runtime.workingDir}}
12064
- Environment: {{runtime.environment}}
12065
-
12066
- ## Task Guidance
12067
-
12068
- - Use tools when needed
12069
- - Explain your reasoning clearly
12070
- - Ask clarifying questions when requirements are ambiguous
12071
- - Never claim a file/tool change unless the corresponding tool call actually succeeded
12072
- `;
12047
+ var AGENT_TEMPLATE = (name, id, options) => defaultAgentDefinition({
12048
+ name,
12049
+ id,
12050
+ modelProvider: options.modelProvider,
12051
+ modelName: options.modelName
12052
+ });
12073
12053
  var resolveLocalPackagesRoot = () => {
12074
12054
  const candidate = resolve3(__dirname, "..", "..", "harness", "package.json");
12075
12055
  if (existsSync(candidate)) {
@@ -14261,7 +14241,7 @@ var runInteractive = async (workingDir, params) => {
14261
14241
  await harness.initialize();
14262
14242
  const identity = await ensureAgentIdentity2(workingDir);
14263
14243
  try {
14264
- const { runInteractiveInk } = await import("./run-interactive-ink-LJTKUUV4.js");
14244
+ const { runInteractiveInk } = await import("./run-interactive-ink-3BKXJ5SX.js");
14265
14245
  await runInteractiveInk({
14266
14246
  harness,
14267
14247
  params,
@@ -14307,7 +14287,6 @@ var approvalLog = createLogger("approval");
14307
14287
  var browserLog = createLogger("browser");
14308
14288
  var selfFetchLog = createLogger("self-fetch");
14309
14289
  var csrfLog = createLogger("csrf");
14310
- var uploadLog = createLogger("upload");
14311
14290
  var collectToolCallIds = (msgs) => {
14312
14291
  const ids = /* @__PURE__ */ new Set();
14313
14292
  for (const m of msgs) {
@@ -16826,7 +16805,8 @@ data: ${JSON.stringify(frame)}
16826
16805
  return;
16827
16806
  }
16828
16807
  if (pathname === "/api/slash-commands" && request.method === "GET") {
16829
- const skills = harness.listSkills().map((s) => ({
16808
+ const tenantSkills = await harness.listSkillsForTenant(ctx.tenantId);
16809
+ const skills = tenantSkills.map((s) => ({
16830
16810
  command: "/" + s.name,
16831
16811
  description: s.description,
16832
16812
  type: "skill"
@@ -16964,7 +16944,7 @@ data: ${JSON.stringify(frame)}
16964
16944
  const conversationMessageMatch = pathname.match(/^\/api\/conversations\/([^/]+)\/messages$/);
16965
16945
  if (conversationMessageMatch && request.method === "POST") {
16966
16946
  const conversationId = decodeURIComponent(conversationMessageMatch[1] ?? "");
16967
- const conversation = await conversationStore.getWithArchive(conversationId);
16947
+ const conversation = await conversationStore.get(conversationId);
16968
16948
  if (!conversation || !canAccessConversation(conversation)) {
16969
16949
  writeJson(response, 404, {
16970
16950
  code: "CONVERSATION_NOT_FOUND",
@@ -17025,6 +17005,8 @@ data: ${JSON.stringify(frame)}
17025
17005
  });
17026
17006
  if (conversation.messages.length === 0 && (conversation.title === "New conversation" || conversation.title.trim().length === 0)) {
17027
17007
  conversation.title = inferConversationTitle(messageText);
17008
+ conversation.updatedAt = Date.now();
17009
+ await conversationStore.update(conversation);
17028
17010
  }
17029
17011
  response.writeHead(200, {
17030
17012
  "Content-Type": "text/event-stream",
@@ -17032,53 +17014,6 @@ data: ${JSON.stringify(frame)}
17032
17014
  Connection: "keep-alive",
17033
17015
  "X-Accel-Buffering": "no"
17034
17016
  });
17035
- const canonicalHistory = resolveRunRequest(conversation, {
17036
- conversationId,
17037
- messages: conversation.messages
17038
- });
17039
- const shouldRebuildCanonical = canonicalHistory.shouldRebuildCanonical;
17040
- const harnessMessages = [...canonicalHistory.messages];
17041
- const historyMessages = [...conversation.messages];
17042
- const preRunMessages = [...conversation.messages];
17043
- log.debug(
17044
- `conversation=${conversationId.slice(0, 8)} history=${canonicalHistory.source}`
17045
- );
17046
- let latestRunId = conversation.runtimeRunId ?? "";
17047
- let userContent = messageText;
17048
- if (files.length > 0) {
17049
- try {
17050
- const uploadedParts = await Promise.all(
17051
- files.map(async (f) => {
17052
- const buf = Buffer.from(f.data, "base64");
17053
- const key = deriveUploadKey(buf, f.mediaType);
17054
- const ref = await uploadStore.put(key, buf, f.mediaType);
17055
- return {
17056
- type: "file",
17057
- data: ref,
17058
- mediaType: f.mediaType,
17059
- filename: f.filename
17060
- };
17061
- })
17062
- );
17063
- userContent = [
17064
- { type: "text", text: messageText },
17065
- ...uploadedParts
17066
- ];
17067
- } catch (uploadErr) {
17068
- const errMsg = uploadErr instanceof Error ? uploadErr.message : String(uploadErr);
17069
- uploadLog.error(`file upload failed: ${errMsg}`);
17070
- const errorEvent = {
17071
- type: "run:error",
17072
- runId: "",
17073
- error: { code: "UPLOAD_ERROR", message: `File upload failed: ${errMsg}` }
17074
- };
17075
- broadcastEvent(conversationId, errorEvent);
17076
- finishConversationStream(conversationId);
17077
- activeConversationRuns.delete(conversationId);
17078
- response.end();
17079
- return;
17080
- }
17081
- }
17082
17017
  const unsubSubagentEvents = onConversationEvent(conversationId, (evt) => {
17083
17018
  if (evt.type.startsWith("subagent:")) {
17084
17019
  try {
@@ -17087,79 +17022,18 @@ data: ${JSON.stringify(frame)}
17087
17022
  }
17088
17023
  }
17089
17024
  });
17090
- const draft = createTurnDraftState();
17091
- let checkpointedRun = false;
17092
- let runCancelled = false;
17093
- let runContinuationMessages;
17094
- let cancelHarnessMessages;
17095
- const turnTimestamp = Date.now();
17096
- const userMessage = userContent != null ? {
17097
- role: "user",
17098
- content: userContent,
17099
- metadata: { id: randomUUID3(), timestamp: turnTimestamp }
17100
- } : void 0;
17101
- const assistantId = randomUUID3();
17102
- const buildMessages = () => {
17103
- const draftSections = cloneSections(draft.sections);
17104
- if (draft.currentTools.length > 0) {
17105
- draftSections.push({ type: "tools", content: [...draft.currentTools] });
17106
- }
17107
- if (draft.currentText.length > 0) {
17108
- draftSections.push({ type: "text", content: draft.currentText });
17109
- }
17110
- const userTurn = userMessage ? [userMessage] : [];
17111
- const hasDraftContent = draft.assistantResponse.length > 0 || draft.toolTimeline.length > 0 || draftSections.length > 0;
17112
- if (!hasDraftContent) {
17113
- return [...historyMessages, ...userTurn];
17114
- }
17115
- return [
17116
- ...historyMessages,
17117
- ...userTurn,
17118
- {
17119
- role: "assistant",
17120
- content: draft.assistantResponse,
17121
- metadata: buildAssistantMetadata2(draft, draftSections, { id: assistantId, timestamp: turnTimestamp })
17122
- }
17123
- ];
17124
- };
17125
- const persistDraftAssistantTurn = async () => {
17126
- if (draft.assistantResponse.length === 0 && draft.toolTimeline.length === 0) return;
17127
- conversation.messages = buildMessages();
17128
- conversation.updatedAt = Date.now();
17129
- await conversationStore.update(conversation);
17130
- };
17025
+ let latestRunId = "";
17131
17026
  try {
17132
- {
17133
- conversation.messages = [
17134
- ...historyMessages,
17135
- ...userMessage ? [userMessage] : []
17136
- ];
17137
- conversation.subagentCallbackCount = 0;
17138
- conversation._continuationCount = void 0;
17139
- conversation.updatedAt = Date.now();
17140
- conversationStore.update(conversation).catch((err) => {
17141
- log.error(`failed to persist user turn: ${formatError(err)}`);
17142
- });
17143
- }
17144
- const execution = await executeConversationTurn2({
17027
+ const result = await runConversationTurn({
17145
17028
  harness,
17146
- runInput: {
17147
- task: messageText,
17148
- conversationId,
17149
- tenantId: ctx.tenantId ?? void 0,
17150
- parameters: buildTurnParameters(conversation, { bodyParameters }),
17151
- messages: harnessMessages,
17152
- files: files.length > 0 ? files : void 0,
17153
- abortSignal: abortController.signal
17154
- },
17155
- initialContextTokens: conversation.contextTokens ?? 0,
17156
- initialContextWindow: conversation.contextWindow ?? 0,
17157
- onEvent: async (event, eventDraft) => {
17158
- draft.assistantResponse = eventDraft.assistantResponse;
17159
- draft.toolTimeline = eventDraft.toolTimeline;
17160
- draft.sections = eventDraft.sections;
17161
- draft.currentTools = eventDraft.currentTools;
17162
- draft.currentText = eventDraft.currentText;
17029
+ conversationStore,
17030
+ conversationId,
17031
+ task: messageText,
17032
+ files: files.length > 0 ? files : void 0,
17033
+ parameters: buildTurnParameters(conversation, { bodyParameters }),
17034
+ abortSignal: abortController.signal,
17035
+ tenantId: ctx.tenantId ?? void 0,
17036
+ onEvent: async (event) => {
17163
17037
  if (event.type === "run:started") {
17164
17038
  latestRunId = event.runId;
17165
17039
  runOwners.set(event.runId, ownerId);
@@ -17169,98 +17043,12 @@ data: ${JSON.stringify(frame)}
17169
17043
  active.runId = event.runId;
17170
17044
  }
17171
17045
  }
17172
- if (event.type === "run:cancelled") {
17173
- runCancelled = true;
17174
- if (event.messages) cancelHarnessMessages = event.messages;
17175
- }
17176
- if (event.type === "compaction:completed") {
17177
- if (event.compactedMessages) {
17178
- historyMessages.length = 0;
17179
- historyMessages.push(...event.compactedMessages);
17180
- const preservedFromHistory = historyMessages.length - 1;
17181
- const removedCount = preRunMessages.length - Math.max(0, preservedFromHistory);
17182
- const existingHistory = conversation.compactedHistory ?? [];
17183
- conversation.compactedHistory = [
17184
- ...existingHistory,
17185
- ...preRunMessages.slice(0, removedCount)
17186
- ];
17187
- }
17188
- }
17189
- if (event.type === "step:completed") {
17190
- await persistDraftAssistantTurn();
17191
- }
17192
- if (event.type === "tool:approval:required") {
17193
- const toolText = `- approval required \`${event.tool}\``;
17194
- draft.toolTimeline.push(toolText);
17195
- draft.currentTools.push(toolText);
17196
- const existingApprovals = Array.isArray(conversation.pendingApprovals) ? conversation.pendingApprovals : [];
17197
- if (!existingApprovals.some((approval) => approval.approvalId === event.approvalId)) {
17198
- conversation.pendingApprovals = [
17199
- ...existingApprovals,
17200
- {
17201
- approvalId: event.approvalId,
17202
- runId: latestRunId || conversation.runtimeRunId || "",
17203
- tool: event.tool,
17204
- toolCallId: void 0,
17205
- input: event.input ?? {},
17206
- checkpointMessages: void 0,
17207
- baseMessageCount: historyMessages.length,
17208
- pendingToolCalls: []
17209
- }
17210
- ];
17211
- conversation.updatedAt = Date.now();
17212
- await conversationStore.update(conversation);
17213
- }
17214
- await persistDraftAssistantTurn();
17215
- }
17216
- if (event.type === "tool:approval:checkpoint") {
17217
- conversation.messages = buildMessages();
17218
- conversation.pendingApprovals = buildApprovalCheckpoints({
17219
- approvals: event.approvals,
17220
- runId: latestRunId,
17221
- checkpointMessages: event.checkpointMessages,
17222
- baseMessageCount: historyMessages.length,
17223
- pendingToolCalls: event.pendingToolCalls
17224
- });
17225
- conversation._toolResultArchive = harness.getToolResultArchive(conversationId);
17226
- conversation.updatedAt = Date.now();
17227
- await conversationStore.update(conversation);
17228
- checkpointedRun = true;
17229
- }
17230
- if (event.type === "run:completed") {
17231
- if (event.result.continuation && event.result.continuationMessages) {
17232
- runContinuationMessages = event.result.continuationMessages;
17233
- conversation.messages = buildMessages();
17234
- conversation._continuationMessages = runContinuationMessages;
17235
- conversation._harnessMessages = runContinuationMessages;
17236
- conversation._toolResultArchive = harness.getToolResultArchive(conversationId);
17237
- conversation.runtimeRunId = latestRunId || conversation.runtimeRunId;
17238
- if (!checkpointedRun) {
17239
- conversation.pendingApprovals = [];
17240
- }
17241
- if ((event.result.contextTokens ?? 0) > 0) conversation.contextTokens = event.result.contextTokens;
17242
- if ((event.result.contextWindow ?? 0) > 0) conversation.contextWindow = event.result.contextWindow;
17243
- conversation.updatedAt = Date.now();
17244
- await conversationStore.update(conversation);
17245
- if (!checkpointedRun) {
17246
- doWaitUntil(
17247
- new Promise((r) => setTimeout(r, 3e3)).then(
17248
- () => selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(conversationId)}`)
17249
- )
17250
- );
17251
- }
17252
- }
17253
- }
17254
17046
  await telemetry.emit(event);
17255
17047
  let sseEvent = event.type === "compaction:completed" && event.compactedMessages ? { ...event, compactedMessages: void 0 } : event;
17256
17048
  if (sseEvent.type === "run:completed") {
17257
17049
  const hasPendingSubagents = await hasPendingSubagentWorkForParent(conversationId, ownerId);
17258
17050
  const stripped = { ...sseEvent, result: { ...sseEvent.result, continuationMessages: void 0 } };
17259
- if (hasPendingSubagents) {
17260
- sseEvent = { ...stripped, pendingSubagents: true };
17261
- } else {
17262
- sseEvent = stripped;
17263
- }
17051
+ sseEvent = hasPendingSubagents ? { ...stripped, pendingSubagents: true } : stripped;
17264
17052
  }
17265
17053
  broadcastEvent(conversationId, sseEvent);
17266
17054
  try {
@@ -17270,38 +17058,15 @@ data: ${JSON.stringify(frame)}
17270
17058
  emitBrowserStatusIfActive(conversationId, event, response);
17271
17059
  }
17272
17060
  });
17273
- flushTurnDraft2(draft);
17274
- latestRunId = execution.latestRunId || latestRunId;
17275
- if (!checkpointedRun && !runContinuationMessages) {
17276
- conversation.messages = buildMessages();
17277
- applyTurnMetadata(conversation, {
17278
- latestRunId,
17279
- contextTokens: execution.runContextTokens,
17280
- contextWindow: execution.runContextWindow,
17281
- harnessMessages: execution.runHarnessMessages,
17282
- toolResultArchive: harness.getToolResultArchive(conversationId)
17283
- }, { shouldRebuildCanonical });
17284
- await conversationStore.update(conversation);
17061
+ if (result.continuation && !result.checkpointed) {
17062
+ doWaitUntil(
17063
+ new Promise((r) => setTimeout(r, 3e3)).then(
17064
+ () => selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(conversationId)}`)
17065
+ )
17066
+ );
17285
17067
  }
17286
17068
  } catch (error) {
17287
- flushTurnDraft2(draft);
17288
- if (abortController.signal.aborted || runCancelled) {
17289
- if (draft.assistantResponse.length > 0 || draft.toolTimeline.length > 0 || draft.sections.length > 0) {
17290
- conversation.messages = buildMessages();
17291
- applyTurnMetadata(conversation, {
17292
- latestRunId,
17293
- contextTokens: 0,
17294
- contextWindow: 0,
17295
- harnessMessages: cancelHarnessMessages,
17296
- toolResultArchive: harness.getToolResultArchive(conversationId)
17297
- }, { shouldRebuildCanonical: true });
17298
- await conversationStore.update(conversation);
17299
- }
17300
- if (!checkpointedRun) {
17301
- await clearPendingApprovalsForConversation(conversationId);
17302
- }
17303
- return;
17304
- }
17069
+ log.error(`runConversationTurn threw: ${formatError(error)}`);
17305
17070
  try {
17306
17071
  response.write(
17307
17072
  formatSseEvent({
@@ -17314,11 +17079,6 @@ data: ${JSON.stringify(frame)}
17314
17079
  })
17315
17080
  );
17316
17081
  } catch {
17317
- if (draft.assistantResponse.length > 0 || draft.toolTimeline.length > 0 || draft.sections.length > 0) {
17318
- conversation.messages = buildMessages();
17319
- conversation.updatedAt = Date.now();
17320
- await conversationStore.update(conversation);
17321
- }
17322
17082
  }
17323
17083
  } finally {
17324
17084
  unsubSubagentEvents();
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  main
4
- } from "./chunk-KVGMTYDD.js";
4
+ } from "./chunk-4XGZI7KU.js";
5
5
 
6
6
  // src/cli.ts
7
7
  var _originalEmitWarning = process.emitWarning.bind(process);
package/dist/index.d.ts CHANGED
@@ -54,6 +54,13 @@ declare const MAX_PRUNE_PER_RUN = 25;
54
54
  /** Delete old cron conversations beyond `maxRuns`, capped to avoid API storms on catch-up. */
55
55
  declare const pruneCronConversations: (store: ConversationStore, ownerId: string, jobName: string, maxRuns: number) => Promise<number>;
56
56
 
57
+ /**
58
+ * Thin back-compat wrapper around `defaultAgentDefinition` from the harness
59
+ * package. The canonical template now lives in
60
+ * `@poncho-ai/harness/src/default-agent.ts` so SDK consumers can pass the
61
+ * exact same default to `new AgentHarness({ agentDefinition })` without
62
+ * hand-copying the template.
63
+ */
57
64
  declare const AGENT_TEMPLATE: (name: string, id: string, options: {
58
65
  modelProvider: "anthropic" | "openai" | "openai-codex";
59
66
  modelName: string;
package/dist/index.js CHANGED
@@ -77,7 +77,7 @@ import {
77
77
  writeHtml,
78
78
  writeJson,
79
79
  writeScaffoldFile
80
- } from "./chunk-KVGMTYDD.js";
80
+ } from "./chunk-4XGZI7KU.js";
81
81
  export {
82
82
  AGENT_TEMPLATE,
83
83
  ENV_TEMPLATE,
@@ -3,7 +3,7 @@ import {
3
3
  getMascotLines,
4
4
  inferConversationTitle,
5
5
  resolveHarnessEnvironment
6
- } from "./chunk-KVGMTYDD.js";
6
+ } from "./chunk-4XGZI7KU.js";
7
7
 
8
8
  // src/run-interactive-ink.ts
9
9
  import * as readline from "readline";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poncho-ai/cli",
3
- "version": "0.40.2",
3
+ "version": "0.40.4",
4
4
  "description": "CLI for building and deploying AI agents",
5
5
  "repository": {
6
6
  "type": "git",
@@ -28,8 +28,8 @@
28
28
  "react": "^19.2.4",
29
29
  "react-devtools-core": "^6.1.5",
30
30
  "yaml": "^2.8.1",
31
- "@poncho-ai/harness": "0.41.0",
32
31
  "@poncho-ai/messaging": "0.8.5",
32
+ "@poncho-ai/harness": "0.43.0",
33
33
  "@poncho-ai/sdk": "1.10.0"
34
34
  },
35
35
  "devDependencies": {
package/src/index.ts CHANGED
@@ -16,7 +16,6 @@ import {
16
16
  createConversationStore,
17
17
  createConversationStoreFromEngine,
18
18
  createUploadStore,
19
- deriveUploadKey,
20
19
  ensureAgentIdentity,
21
20
  loadPonchoConfig,
22
21
  parseAgentMarkdown,
@@ -39,6 +38,7 @@ import {
39
38
  normalizeApprovalCheckpoint,
40
39
  buildApprovalCheckpoints,
41
40
  applyTurnMetadata,
41
+ runConversationTurn,
42
42
  TOOL_RESULT_ARCHIVE_PARAM,
43
43
  withToolResultArchiveParam,
44
44
  AgentOrchestrator,
@@ -93,7 +93,6 @@ const approvalLog = createLogger("approval");
93
93
  const browserLog = createLogger("browser");
94
94
  const selfFetchLog = createLogger("self-fetch");
95
95
  const csrfLog = createLogger("csrf");
96
- const uploadLog = createLogger("upload");
97
96
 
98
97
  /**
99
98
  * Walk a sequence of harness messages and collect all tool-call ids that
@@ -3209,7 +3208,8 @@ export const createRequestHandler = async (options?: {
3209
3208
  }
3210
3209
 
3211
3210
  if (pathname === "/api/slash-commands" && request.method === "GET") {
3212
- const skills: ApiSlashCommand[] = harness.listSkills().map((s) => ({
3211
+ const tenantSkills = await harness.listSkillsForTenant(ctx.tenantId);
3212
+ const skills: ApiSlashCommand[] = tenantSkills.map((s) => ({
3213
3213
  command: "/" + s.name,
3214
3214
  description: s.description,
3215
3215
  type: "skill" as const,
@@ -3353,9 +3353,9 @@ export const createRequestHandler = async (options?: {
3353
3353
  const conversationMessageMatch = pathname.match(/^\/api\/conversations\/([^/]+)\/messages$/);
3354
3354
  if (conversationMessageMatch && request.method === "POST") {
3355
3355
  const conversationId = decodeURIComponent(conversationMessageMatch[1] ?? "");
3356
- // getWithArchive conversation feeds withToolResultArchiveParam when
3357
- // the turn below calls executeConversationTurn.
3358
- const conversation = await conversationStore.getWithArchive(conversationId);
3356
+ // Light access check first; the helper reloads the conversation
3357
+ // (with archive) when it actually runs the turn.
3358
+ const conversation = await conversationStore.get(conversationId);
3359
3359
  if (!conversation || !canAccessConversation(conversation)) {
3360
3360
  writeJson(response, 404, {
3361
3361
  code: "CONVERSATION_NOT_FOUND",
@@ -3420,159 +3420,46 @@ export const createRequestHandler = async (options?: {
3420
3420
  abortController,
3421
3421
  runId: null,
3422
3422
  });
3423
+
3424
+ // Auto-infer a title for fresh conversations. Persist it before the
3425
+ // helper reloads, so its in-memory copy carries the new title.
3423
3426
  if (
3424
3427
  conversation.messages.length === 0 &&
3425
3428
  (conversation.title === "New conversation" || conversation.title.trim().length === 0)
3426
3429
  ) {
3427
3430
  conversation.title = inferConversationTitle(messageText);
3431
+ conversation.updatedAt = Date.now();
3432
+ await conversationStore.update(conversation);
3428
3433
  }
3434
+
3429
3435
  response.writeHead(200, {
3430
3436
  "Content-Type": "text/event-stream",
3431
3437
  "Cache-Control": "no-cache",
3432
3438
  Connection: "keep-alive",
3433
3439
  "X-Accel-Buffering": "no",
3434
3440
  });
3435
- const canonicalHistory = resolveRunRequest(conversation, {
3436
- conversationId,
3437
- messages: conversation.messages,
3438
- });
3439
- const shouldRebuildCanonical = canonicalHistory.shouldRebuildCanonical;
3440
- const harnessMessages = [...canonicalHistory.messages];
3441
- const historyMessages = [...conversation.messages];
3442
- const preRunMessages = [...conversation.messages];
3443
- log.debug(
3444
- `conversation=${conversationId.slice(0, 8)} history=${canonicalHistory.source}`,
3445
- );
3446
- let latestRunId = conversation.runtimeRunId ?? "";
3447
- let userContent: Message["content"] | undefined = messageText;
3448
- if (files.length > 0) {
3449
- try {
3450
- const uploadedParts = await Promise.all(
3451
- files.map(async (f) => {
3452
- const buf = Buffer.from(f.data, "base64");
3453
- const key = deriveUploadKey(buf, f.mediaType);
3454
- const ref = await uploadStore.put(key, buf, f.mediaType);
3455
- return {
3456
- type: "file" as const,
3457
- data: ref,
3458
- mediaType: f.mediaType,
3459
- filename: f.filename,
3460
- };
3461
- }),
3462
- );
3463
- userContent = [
3464
- { type: "text" as const, text: messageText },
3465
- ...uploadedParts,
3466
- ];
3467
- } catch (uploadErr) {
3468
- const errMsg = uploadErr instanceof Error ? uploadErr.message : String(uploadErr);
3469
- uploadLog.error(`file upload failed: ${errMsg}`);
3470
- const errorEvent: AgentEvent = {
3471
- type: "run:error",
3472
- runId: "",
3473
- error: { code: "UPLOAD_ERROR", message: `File upload failed: ${errMsg}` },
3474
- };
3475
- broadcastEvent(conversationId, errorEvent);
3476
- finishConversationStream(conversationId);
3477
- activeConversationRuns.delete(conversationId);
3478
- response.end();
3479
- return;
3480
- }
3481
- }
3441
+
3442
+ // Forward subagent events out-of-band — they're emitted from
3443
+ // child-conversation runs, not yielded by harness.run for this one.
3482
3444
  const unsubSubagentEvents = onConversationEvent(conversationId, (evt) => {
3483
3445
  if (evt.type.startsWith("subagent:")) {
3484
3446
  try { response.write(formatSseEvent(evt)); } catch {}
3485
3447
  }
3486
3448
  });
3487
3449
 
3488
- const draft = createTurnDraftState();
3489
- let checkpointedRun = false;
3490
- let runCancelled = false;
3491
- let runContinuationMessages: Message[] | undefined;
3492
- // Snapshot of the harness's in-flight messages emitted with run:cancelled,
3493
- // so the catch-path (executeConversationTurn threw) can still persist a
3494
- // canonical history that includes the cancelled work.
3495
- let cancelHarnessMessages: Message[] | undefined;
3496
-
3497
- // Hoist stable ids for this turn. The same userMessage / assistantId is
3498
- // reused across every buildMessages() call so the in-flight assistant
3499
- // bubble keeps a stable metadata.id from its very first persisted byte.
3500
- const turnTimestamp = Date.now();
3501
- const userMessage: Message | undefined = userContent != null
3502
- ? {
3503
- role: "user" as const,
3504
- content: userContent,
3505
- metadata: { id: randomUUID(), timestamp: turnTimestamp },
3506
- }
3507
- : undefined;
3508
- const assistantId = randomUUID();
3509
-
3510
- const buildMessages = (): Message[] => {
3511
- const draftSections = cloneSections(draft.sections);
3512
- if (draft.currentTools.length > 0) {
3513
- draftSections.push({ type: "tools", content: [...draft.currentTools] });
3514
- }
3515
- if (draft.currentText.length > 0) {
3516
- draftSections.push({ type: "text", content: draft.currentText });
3517
- }
3518
- const userTurn: Message[] = userMessage ? [userMessage] : [];
3519
- const hasDraftContent =
3520
- draft.assistantResponse.length > 0 || draft.toolTimeline.length > 0 || draftSections.length > 0;
3521
- if (!hasDraftContent) {
3522
- return [...historyMessages, ...userTurn];
3523
- }
3524
- return [
3525
- ...historyMessages,
3526
- ...userTurn,
3527
- {
3528
- role: "assistant" as const,
3529
- content: draft.assistantResponse,
3530
- metadata: buildAssistantMetadata(draft, draftSections, { id: assistantId, timestamp: turnTimestamp }),
3531
- },
3532
- ];
3533
- };
3534
-
3535
- const persistDraftAssistantTurn = async (): Promise<void> => {
3536
- if (draft.assistantResponse.length === 0 && draft.toolTimeline.length === 0) return;
3537
- conversation.messages = buildMessages();
3538
- conversation.updatedAt = Date.now();
3539
- await conversationStore.update(conversation);
3540
- };
3450
+ let latestRunId = "";
3541
3451
 
3542
3452
  try {
3543
- {
3544
- conversation.messages = [
3545
- ...historyMessages,
3546
- ...(userMessage ? [userMessage] : []),
3547
- ];
3548
- conversation.subagentCallbackCount = 0;
3549
- conversation._continuationCount = undefined;
3550
- conversation.updatedAt = Date.now();
3551
- conversationStore.update(conversation).catch((err) => {
3552
- log.error(`failed to persist user turn: ${formatError(err)}`);
3553
- });
3554
- }
3555
-
3556
- const execution = await executeConversationTurn({
3453
+ const result = await runConversationTurn({
3557
3454
  harness,
3558
- runInput: {
3559
- task: messageText,
3560
- conversationId,
3561
- tenantId: ctx.tenantId ?? undefined,
3562
- parameters: buildTurnParameters(conversation, { bodyParameters }),
3563
- messages: harnessMessages,
3564
- files: files.length > 0 ? files : undefined,
3565
- abortSignal: abortController.signal,
3566
- },
3567
- initialContextTokens: conversation.contextTokens ?? 0,
3568
- initialContextWindow: conversation.contextWindow ?? 0,
3569
- onEvent: async (event, eventDraft) => {
3570
- draft.assistantResponse = eventDraft.assistantResponse;
3571
- draft.toolTimeline = eventDraft.toolTimeline;
3572
- draft.sections = eventDraft.sections;
3573
- draft.currentTools = eventDraft.currentTools;
3574
- draft.currentText = eventDraft.currentText;
3575
-
3455
+ conversationStore,
3456
+ conversationId,
3457
+ task: messageText,
3458
+ files: files.length > 0 ? files : undefined,
3459
+ parameters: buildTurnParameters(conversation, { bodyParameters }),
3460
+ abortSignal: abortController.signal,
3461
+ tenantId: ctx.tenantId ?? undefined,
3462
+ onEvent: async (event) => {
3576
3463
  if (event.type === "run:started") {
3577
3464
  latestRunId = event.runId;
3578
3465
  runOwners.set(event.runId, ownerId);
@@ -3582,154 +3469,36 @@ export const createRequestHandler = async (options?: {
3582
3469
  active.runId = event.runId;
3583
3470
  }
3584
3471
  }
3585
- if (event.type === "run:cancelled") {
3586
- runCancelled = true;
3587
- if (event.messages) cancelHarnessMessages = event.messages;
3588
- }
3589
- if (event.type === "compaction:completed") {
3590
- if (event.compactedMessages) {
3591
- historyMessages.length = 0;
3592
- historyMessages.push(...event.compactedMessages);
3593
-
3594
- const preservedFromHistory = historyMessages.length - 1;
3595
- const removedCount = preRunMessages.length - Math.max(0, preservedFromHistory);
3596
- const existingHistory = conversation.compactedHistory ?? [];
3597
- conversation.compactedHistory = [
3598
- ...existingHistory,
3599
- ...preRunMessages.slice(0, removedCount),
3600
- ];
3601
- }
3602
- }
3603
- if (event.type === "step:completed") {
3604
- await persistDraftAssistantTurn();
3605
- }
3606
- if (event.type === "tool:approval:required") {
3607
- const toolText = `- approval required \`${event.tool}\``;
3608
- draft.toolTimeline.push(toolText);
3609
- draft.currentTools.push(toolText);
3610
- const existingApprovals = Array.isArray(conversation.pendingApprovals)
3611
- ? conversation.pendingApprovals
3612
- : [];
3613
- if (!existingApprovals.some((approval) => approval.approvalId === event.approvalId)) {
3614
- conversation.pendingApprovals = [
3615
- ...existingApprovals,
3616
- {
3617
- approvalId: event.approvalId,
3618
- runId: latestRunId || conversation.runtimeRunId || "",
3619
- tool: event.tool,
3620
- toolCallId: undefined,
3621
- input: (event.input ?? {}) as Record<string, unknown>,
3622
- checkpointMessages: undefined,
3623
- baseMessageCount: historyMessages.length,
3624
- pendingToolCalls: [],
3625
- },
3626
- ];
3627
- conversation.updatedAt = Date.now();
3628
- await conversationStore.update(conversation);
3629
- }
3630
- await persistDraftAssistantTurn();
3631
- }
3632
- if (event.type === "tool:approval:checkpoint") {
3633
- conversation.messages = buildMessages();
3634
- conversation.pendingApprovals = buildApprovalCheckpoints({
3635
- approvals: event.approvals,
3636
- runId: latestRunId,
3637
- checkpointMessages: event.checkpointMessages,
3638
- baseMessageCount: historyMessages.length,
3639
- pendingToolCalls: event.pendingToolCalls,
3640
- });
3641
- conversation._toolResultArchive = harness.getToolResultArchive(conversationId);
3642
- conversation.updatedAt = Date.now();
3643
- await conversationStore.update(conversation);
3644
- checkpointedRun = true;
3645
- }
3646
- if (event.type === "run:completed") {
3647
- if (event.result.continuation && event.result.continuationMessages) {
3648
- runContinuationMessages = event.result.continuationMessages;
3649
-
3650
- conversation.messages = buildMessages();
3651
- conversation._continuationMessages = runContinuationMessages;
3652
- conversation._harnessMessages = runContinuationMessages;
3653
- conversation._toolResultArchive = harness.getToolResultArchive(conversationId);
3654
- conversation.runtimeRunId = latestRunId || conversation.runtimeRunId;
3655
- if (!checkpointedRun) {
3656
- conversation.pendingApprovals = [];
3657
- }
3658
- if ((event.result.contextTokens ?? 0) > 0) conversation.contextTokens = event.result.contextTokens!;
3659
- if ((event.result.contextWindow ?? 0) > 0) conversation.contextWindow = event.result.contextWindow!;
3660
- conversation.updatedAt = Date.now();
3661
- await conversationStore.update(conversation);
3662
-
3663
- if (!checkpointedRun) {
3664
- doWaitUntil(
3665
- new Promise(r => setTimeout(r, 3000)).then(() =>
3666
- selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(conversationId)}`),
3667
- ),
3668
- );
3669
- }
3670
- }
3671
- }
3672
-
3673
3472
  await telemetry.emit(event);
3473
+ // Strip large internal payloads before sending over the wire.
3674
3474
  let sseEvent: AgentEvent = event.type === "compaction:completed" && event.compactedMessages
3675
3475
  ? { ...event, compactedMessages: undefined }
3676
3476
  : event;
3677
3477
  if (sseEvent.type === "run:completed") {
3678
3478
  const hasPendingSubagents = await hasPendingSubagentWorkForParent(conversationId, ownerId);
3679
3479
  const stripped = { ...sseEvent, result: { ...sseEvent.result, continuationMessages: undefined } };
3680
- if (hasPendingSubagents) {
3681
- sseEvent = { ...stripped, pendingSubagents: true };
3682
- } else {
3683
- sseEvent = stripped;
3684
- }
3480
+ sseEvent = hasPendingSubagents ? { ...stripped, pendingSubagents: true } : stripped;
3685
3481
  }
3686
3482
  broadcastEvent(conversationId, sseEvent);
3687
- try {
3688
- response.write(formatSseEvent(sseEvent));
3689
- } catch {
3690
- // Client disconnected — continue processing so the run completes.
3691
- }
3483
+ try { response.write(formatSseEvent(sseEvent)); } catch {}
3692
3484
  emitBrowserStatusIfActive(conversationId, event, response);
3693
3485
  },
3694
3486
  });
3695
3487
 
3696
- flushTurnDraft(draft);
3697
- latestRunId = execution.latestRunId || latestRunId;
3698
-
3699
- if (!checkpointedRun && !runContinuationMessages) {
3700
- conversation.messages = buildMessages();
3701
- applyTurnMetadata(conversation, {
3702
- latestRunId,
3703
- contextTokens: execution.runContextTokens,
3704
- contextWindow: execution.runContextWindow,
3705
- harnessMessages: execution.runHarnessMessages,
3706
- toolResultArchive: harness.getToolResultArchive(conversationId),
3707
- }, { shouldRebuildCanonical });
3708
- await conversationStore.update(conversation);
3488
+ // Trigger the auto-continuation HTTP roundtrip so the next run
3489
+ // queues. Helper has already persisted the continuation messages.
3490
+ if (result.continuation && !result.checkpointed) {
3491
+ doWaitUntil(
3492
+ new Promise(r => setTimeout(r, 3000)).then(() =>
3493
+ selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(conversationId)}`),
3494
+ ),
3495
+ );
3709
3496
  }
3710
3497
  } catch (error) {
3711
- flushTurnDraft(draft);
3712
- if (abortController.signal.aborted || runCancelled) {
3713
- if (draft.assistantResponse.length > 0 || draft.toolTimeline.length > 0 || draft.sections.length > 0) {
3714
- conversation.messages = buildMessages();
3715
- // Keep _harnessMessages aligned with what the model just saw.
3716
- // Without this, loadCanonicalHistory will hand the next turn a
3717
- // pre-cancellation snapshot and the agent will have no memory of
3718
- // the work it just did.
3719
- applyTurnMetadata(conversation, {
3720
- latestRunId,
3721
- contextTokens: 0,
3722
- contextWindow: 0,
3723
- harnessMessages: cancelHarnessMessages,
3724
- toolResultArchive: harness.getToolResultArchive(conversationId),
3725
- }, { shouldRebuildCanonical: true });
3726
- await conversationStore.update(conversation);
3727
- }
3728
- if (!checkpointedRun) {
3729
- await clearPendingApprovalsForConversation(conversationId);
3730
- }
3731
- return;
3732
- }
3498
+ // The helper handles cancel/error internally and returns gracefully.
3499
+ // This catches anything that escapes — typically a helper bug or
3500
+ // the conversation being deleted between auth check and helper reload.
3501
+ log.error(`runConversationTurn threw: ${formatError(error)}`);
3733
3502
  try {
3734
3503
  response.write(
3735
3504
  formatSseEvent({
@@ -3741,13 +3510,7 @@ export const createRequestHandler = async (options?: {
3741
3510
  },
3742
3511
  }),
3743
3512
  );
3744
- } catch {
3745
- if (draft.assistantResponse.length > 0 || draft.toolTimeline.length > 0 || draft.sections.length > 0) {
3746
- conversation.messages = buildMessages();
3747
- conversation.updatedAt = Date.now();
3748
- await conversationStore.update(conversation);
3749
- }
3750
- }
3513
+ } catch {}
3751
3514
  } finally {
3752
3515
  unsubSubagentEvents();
3753
3516
  const active = activeConversationRuns.get(conversationId);
package/src/templates.ts CHANGED
@@ -2,6 +2,7 @@ import { readFile } from "node:fs/promises";
2
2
  import { existsSync } from "node:fs";
3
3
  import { dirname, relative, resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
+ import { defaultAgentDefinition } from "@poncho-ai/harness";
5
6
 
6
7
  const __dirname = dirname(fileURLToPath(import.meta.url));
7
8
  const packageRoot = resolve(__dirname, "..");
@@ -21,37 +22,24 @@ const readCliVersion = async (): Promise<string> => {
21
22
  return fallback;
22
23
  };
23
24
 
25
+ /**
26
+ * Thin back-compat wrapper around `defaultAgentDefinition` from the harness
27
+ * package. The canonical template now lives in
28
+ * `@poncho-ai/harness/src/default-agent.ts` so SDK consumers can pass the
29
+ * exact same default to `new AgentHarness({ agentDefinition })` without
30
+ * hand-copying the template.
31
+ */
24
32
  export const AGENT_TEMPLATE = (
25
33
  name: string,
26
34
  id: string,
27
35
  options: { modelProvider: "anthropic" | "openai" | "openai-codex"; modelName: string },
28
- ): string => `---
29
- name: ${name}
30
- id: ${id}
31
- description: A helpful Poncho assistant
32
- model:
33
- provider: ${options.modelProvider}
34
- name: ${options.modelName}
35
- temperature: 0.2
36
- limits:
37
- maxSteps: 20
38
- timeout: 300
39
- ---
40
-
41
- # {{name}}
42
-
43
- You are **{{name}}**, a helpful assistant built with Poncho.
44
-
45
- Working directory: {{runtime.workingDir}}
46
- Environment: {{runtime.environment}}
47
-
48
- ## Task Guidance
49
-
50
- - Use tools when needed
51
- - Explain your reasoning clearly
52
- - Ask clarifying questions when requirements are ambiguous
53
- - Never claim a file/tool change unless the corresponding tool call actually succeeded
54
- `;
36
+ ): string =>
37
+ defaultAgentDefinition({
38
+ name,
39
+ id,
40
+ modelProvider: options.modelProvider,
41
+ modelName: options.modelName,
42
+ });
55
43
 
56
44
  /**
57
45
  * Resolve the monorepo packages root if we're running from a local dev build.
@@ -439,7 +439,7 @@ export const WEB_UI_STYLES = `
439
439
  .sidebar-segmented {
440
440
  display: inline-flex;
441
441
  align-self: stretch;
442
- margin: 12px 6px 0;
442
+ margin: 12px 6px 8px;
443
443
  padding: 3px;
444
444
  background: var(--surface-3);
445
445
  border-radius: 999px;