@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.
- package/.turbo/turbo-build.log +6 -6
- package/CHANGELOG.md +91 -0
- package/dist/{chunk-KVGMTYDD.js → chunk-4XGZI7KU.js} +33 -273
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.js +1 -1
- package/dist/{run-interactive-ink-LJTKUUV4.js → run-interactive-ink-3BKXJ5SX.js} +1 -1
- package/package.json +2 -2
- package/src/index.ts +41 -278
- package/src/templates.ts +15 -27
- package/src/web-ui-styles.ts +1 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @poncho-ai/cli@0.40.
|
|
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
|
[34mCLI[39m Building entry: src/cli.ts, src/index.ts
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
[34mESM[39m Build start
|
|
10
10
|
[32mESM[39m [1mdist/cli.js [22m[32m528.00 B[39m
|
|
11
11
|
[32mESM[39m [1mdist/index.js [22m[32m3.10 KB[39m
|
|
12
|
-
[32mESM[39m [1mdist/run-interactive-ink-
|
|
13
|
-
[32mESM[39m [1mdist/chunk-
|
|
14
|
-
[32mESM[39m ⚡️ Build success in
|
|
12
|
+
[32mESM[39m [1mdist/run-interactive-ink-3BKXJ5SX.js [22m[32m23.38 KB[39m
|
|
13
|
+
[32mESM[39m [1mdist/chunk-4XGZI7KU.js [22m[32m664.72 KB[39m
|
|
14
|
+
[32mESM[39m ⚡️ Build success in 75ms
|
|
15
15
|
[34mDTS[39m Build start
|
|
16
|
-
[32mDTS[39m ⚡️ Build success in
|
|
16
|
+
[32mDTS[39m ⚡️ Build success in 4000ms
|
|
17
17
|
[32mDTS[39m [1mdist/cli.d.ts [22m[32m20.00 B[39m
|
|
18
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[32m13.
|
|
18
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m13.56 KB[39m
|
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
|
|
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
|
|
12048
|
-
id
|
|
12049
|
-
|
|
12050
|
-
|
|
12051
|
-
|
|
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-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
17147
|
-
|
|
17148
|
-
|
|
17149
|
-
|
|
17150
|
-
|
|
17151
|
-
|
|
17152
|
-
|
|
17153
|
-
|
|
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
|
-
|
|
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
|
-
|
|
17274
|
-
|
|
17275
|
-
|
|
17276
|
-
|
|
17277
|
-
|
|
17278
|
-
|
|
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
|
-
|
|
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
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poncho-ai/cli",
|
|
3
|
-
"version": "0.40.
|
|
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
|
|
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
|
-
//
|
|
3357
|
-
//
|
|
3358
|
-
const conversation = await conversationStore.
|
|
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
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
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
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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.
|
package/src/web-ui-styles.ts
CHANGED