@poncho-ai/harness 0.41.0 → 0.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +5 -5
- package/CHANGELOG.md +87 -0
- package/dist/index.d.ts +85 -2
- package/dist/index.js +367 -2
- package/package.json +1 -1
- package/src/default-agent.ts +89 -0
- package/src/harness.ts +7 -0
- package/src/index.ts +1 -0
- package/src/orchestrator/index.ts +6 -0
- package/src/orchestrator/run-conversation-turn.ts +420 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @poncho-ai/harness@0.
|
|
2
|
+
> @poncho-ai/harness@0.43.0 build /home/runner/work/poncho-ai/poncho-ai/packages/harness
|
|
3
3
|
> node scripts/embed-docs.js && tsup src/index.ts --format esm --dts
|
|
4
4
|
|
|
5
5
|
[embed-docs] Generated poncho-docs.ts with 4 topics
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
[34mCLI[39m tsup v8.5.1
|
|
9
9
|
[34mCLI[39m Target: es2022
|
|
10
10
|
[34mESM[39m Build start
|
|
11
|
-
[32mESM[39m [1mdist/index.js [22m[32m493.42 KB[39m
|
|
12
11
|
[32mESM[39m [1mdist/isolate-VY35DGLM.js [22m[32m49.43 KB[39m
|
|
13
|
-
[32mESM[39m
|
|
12
|
+
[32mESM[39m [1mdist/index.js [22m[32m506.40 KB[39m
|
|
13
|
+
[32mESM[39m ⚡️ Build success in 222ms
|
|
14
14
|
[34mDTS[39m Build start
|
|
15
|
-
[32mDTS[39m ⚡️ Build success in
|
|
16
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[
|
|
15
|
+
[32mDTS[39m ⚡️ Build success in 6907ms
|
|
16
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m80.85 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,92 @@
|
|
|
1
1
|
# @poncho-ai/harness
|
|
2
2
|
|
|
3
|
+
## 0.43.0
|
|
4
|
+
|
|
5
|
+
### Minor 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
|
+
## 0.42.0
|
|
40
|
+
|
|
41
|
+
### Minor Changes
|
|
42
|
+
|
|
43
|
+
- [`39793b0`](https://github.com/cesr/poncho-ai/commit/39793b0ab11ed26f140af6fc9c0cd3e1b1c83fec) Thanks [@cesr](https://github.com/cesr)! - harness: extract `runConversationTurn` helper; refactor CLI to use it
|
|
44
|
+
|
|
45
|
+
Lifts the inline turn lifecycle from the CLI's
|
|
46
|
+
`POST /api/conversations/:id/messages` handler (~280 lines of orchestration)
|
|
47
|
+
into a new public helper at `@poncho-ai/harness`.
|
|
48
|
+
|
|
49
|
+
The helper handles the full conversation lifecycle for a primary chat
|
|
50
|
+
turn: load the conversation with archive, resolve canonical history,
|
|
51
|
+
upload files via the harness's upload store, build stable user/assistant
|
|
52
|
+
ids, persist the user message immediately, drive `executeConversationTurn`,
|
|
53
|
+
periodically persist the in-flight assistant draft on `step:completed`
|
|
54
|
+
and `tool:approval:required`, persist on `tool:approval:checkpoint` and
|
|
55
|
+
`run:completed` continuation, rebuild history on `compaction:completed`,
|
|
56
|
+
apply turn metadata on success, and persist partial state on
|
|
57
|
+
cancel/error.
|
|
58
|
+
|
|
59
|
+
Caller responsibilities (auth, active-run dedup, streaming, continuation
|
|
60
|
+
HTTP self-fetch, title inference) stay outside the helper — passed in
|
|
61
|
+
via opts or handled around the call. `opts.onEvent` is invoked for every
|
|
62
|
+
`AgentEvent` for downstream forwarding (SSE, WebSocket, telemetry, etc.).
|
|
63
|
+
|
|
64
|
+
The CLI's handler now delegates to `runConversationTurn` (drops from
|
|
65
|
+
~430 to ~150 lines). Consumers like PonchOS can call the same helper
|
|
66
|
+
to ship the _exact_ same conversation lifecycle without duplicating
|
|
67
|
+
the orchestration.
|
|
68
|
+
|
|
69
|
+
Public API additions:
|
|
70
|
+
- `runConversationTurn(opts): Promise<RunConversationTurnResult>`
|
|
71
|
+
- `RunConversationTurnOpts`
|
|
72
|
+
- `RunConversationTurnResult`
|
|
73
|
+
|
|
74
|
+
No behavior changes. The helper is a verbatim extraction of the CLI's
|
|
75
|
+
prior inline implementation.
|
|
76
|
+
|
|
77
|
+
### Patch Changes
|
|
78
|
+
|
|
79
|
+
- [`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
|
|
80
|
+
|
|
81
|
+
The `/api/slash-commands` endpoint was returning only repo-loaded skills,
|
|
82
|
+
so tenant-authored skills stored in the VFS (`/skills/<name>/SKILL.md`)
|
|
83
|
+
never appeared in the `/` autocomplete bar even though the agent could
|
|
84
|
+
already see and run them at conversation time.
|
|
85
|
+
|
|
86
|
+
The endpoint now resolves skills per-tenant via a new
|
|
87
|
+
`harness.listSkillsForTenant(tenantId)` and applies the same repo-wins
|
|
88
|
+
collision semantics used elsewhere in the harness.
|
|
89
|
+
|
|
3
90
|
## 0.41.0
|
|
4
91
|
|
|
5
92
|
### Minor Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { LanguageModel } from 'ai';
|
|
2
2
|
import * as _poncho_ai_sdk from '@poncho-ai/sdk';
|
|
3
|
-
import { Message, ToolContext, ToolDefinition, JsonSchema, RunResult, AgentFailure, RunInput, AgentEvent } from '@poncho-ai/sdk';
|
|
3
|
+
import { Message, ToolContext, ToolDefinition, JsonSchema, RunResult, AgentFailure, RunInput, AgentEvent, FileInput } from '@poncho-ai/sdk';
|
|
4
4
|
export { ToolDefinition, defineTool } from '@poncho-ai/sdk';
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
import { IFileSystem, BufferEncoding, FsStat, FileContent, MkdirOptions, RmOptions, CpOptions, Bash } from 'just-bash';
|
|
@@ -685,6 +685,43 @@ declare const resolveStateConfig: (config: PonchoConfig | undefined) => StateCon
|
|
|
685
685
|
declare const resolveMemoryConfig: (config: PonchoConfig | undefined) => MemoryConfig | undefined;
|
|
686
686
|
declare const loadPonchoConfig: (workingDir: string) => Promise<PonchoConfig | undefined>;
|
|
687
687
|
|
|
688
|
+
interface DefaultAgentDefinitionOptions {
|
|
689
|
+
/** Display name for the agent. Default: "agent". */
|
|
690
|
+
name?: string;
|
|
691
|
+
/**
|
|
692
|
+
* Stable identifier embedded in the frontmatter. Default: a fresh
|
|
693
|
+
* `agent_<32hex>`. Note: when an injected `StorageEngine` is also passed
|
|
694
|
+
* to `AgentHarness`, the engine's `agentId` overrides this at runtime, so
|
|
695
|
+
* SDK consumers can leave it default.
|
|
696
|
+
*/
|
|
697
|
+
id?: string;
|
|
698
|
+
/** Frontmatter description. Default: "A helpful Poncho assistant". */
|
|
699
|
+
description?: string;
|
|
700
|
+
/** Model provider. Default: "anthropic". */
|
|
701
|
+
modelProvider?: "anthropic" | "openai" | "openai-codex";
|
|
702
|
+
/** Model name. Default: "claude-opus-4-5". */
|
|
703
|
+
modelName?: string;
|
|
704
|
+
/** Sampling temperature. Default: 0.2. */
|
|
705
|
+
temperature?: number;
|
|
706
|
+
/** Max tool-call steps per run. Default: 20. */
|
|
707
|
+
maxSteps?: number;
|
|
708
|
+
/** Hard timeout in seconds. Default: 300. */
|
|
709
|
+
timeout?: number;
|
|
710
|
+
}
|
|
711
|
+
declare const DEFAULT_AGENT_NAME = "agent";
|
|
712
|
+
declare const DEFAULT_AGENT_DESCRIPTION = "A helpful Poncho assistant";
|
|
713
|
+
declare const DEFAULT_MODEL_PROVIDER: "anthropic";
|
|
714
|
+
declare const DEFAULT_MODEL_NAME = "claude-opus-4-5";
|
|
715
|
+
declare const DEFAULT_TEMPERATURE = 0.2;
|
|
716
|
+
declare const DEFAULT_MAX_STEPS = 20;
|
|
717
|
+
declare const DEFAULT_TIMEOUT = 300;
|
|
718
|
+
/**
|
|
719
|
+
* Returns the canonical default agent definition as a markdown string,
|
|
720
|
+
* ready to pass to `new AgentHarness({ agentDefinition })`. This is the
|
|
721
|
+
* exact same template `poncho init` writes to `AGENT.md`.
|
|
722
|
+
*/
|
|
723
|
+
declare const defaultAgentDefinition: (opts?: DefaultAgentDefinitionOptions) => string;
|
|
724
|
+
|
|
688
725
|
declare const createDefaultTools: (workingDir: string) => ToolDefinition[];
|
|
689
726
|
declare const createWriteTool: (workingDir: string) => ToolDefinition;
|
|
690
727
|
declare const createEditTool: (workingDir: string) => ToolDefinition;
|
|
@@ -1186,6 +1223,10 @@ declare class AgentHarness {
|
|
|
1186
1223
|
name: string;
|
|
1187
1224
|
description: string;
|
|
1188
1225
|
}>;
|
|
1226
|
+
listSkillsForTenant(tenantId: string | undefined | null): Promise<Array<{
|
|
1227
|
+
name: string;
|
|
1228
|
+
description: string;
|
|
1229
|
+
}>>;
|
|
1189
1230
|
/**
|
|
1190
1231
|
* Wraps the run() generator with an OTel root span (invoke_agent) so all
|
|
1191
1232
|
* child spans (LLM calls via AI SDK, tool execution) group under one trace.
|
|
@@ -1879,4 +1920,46 @@ declare class AgentOrchestrator {
|
|
|
1879
1920
|
recoverStaleSubagents(): Promise<void>;
|
|
1880
1921
|
}
|
|
1881
1922
|
|
|
1882
|
-
|
|
1923
|
+
interface RunConversationTurnOpts {
|
|
1924
|
+
/** Initialised harness instance. */
|
|
1925
|
+
harness: AgentHarness;
|
|
1926
|
+
/** Conversation store backing the turn (typically `engine.conversations` from a StorageEngine). */
|
|
1927
|
+
conversationStore: ConversationStore;
|
|
1928
|
+
conversationId: string;
|
|
1929
|
+
/** The user's new message text. Required (use `""` if you only want to attach files). */
|
|
1930
|
+
task: string;
|
|
1931
|
+
/**
|
|
1932
|
+
* Optional file attachments (FileInput.data is base64 / data URI / https URL).
|
|
1933
|
+
* Files are uploaded via `harness.uploadStore` first so the persisted user
|
|
1934
|
+
* message references stable URLs instead of fat base64 blobs.
|
|
1935
|
+
*/
|
|
1936
|
+
files?: FileInput[];
|
|
1937
|
+
/**
|
|
1938
|
+
* Extra parameters merged into runInput.parameters. Use this for recall
|
|
1939
|
+
* corpus, archive lookup keys, messaging metadata, etc. Do NOT include
|
|
1940
|
+
* `__activeConversationId`, `__ownerId`, or the tool-result-archive — the
|
|
1941
|
+
* helper sets those itself.
|
|
1942
|
+
*/
|
|
1943
|
+
parameters?: Record<string, unknown>;
|
|
1944
|
+
abortSignal?: AbortSignal;
|
|
1945
|
+
tenantId?: string | null;
|
|
1946
|
+
/** Per-event hook — called for every AgentEvent yielded by the run, in order. */
|
|
1947
|
+
onEvent?: (event: AgentEvent) => void | Promise<void>;
|
|
1948
|
+
}
|
|
1949
|
+
interface RunConversationTurnResult {
|
|
1950
|
+
/** runId of the most recent run started during this turn. */
|
|
1951
|
+
latestRunId: string;
|
|
1952
|
+
/** True if the run was cancelled (via abortSignal or run:cancelled event). */
|
|
1953
|
+
cancelled: boolean;
|
|
1954
|
+
/** True if the run errored. The error has been emitted via onEvent as run:error. */
|
|
1955
|
+
errored: boolean;
|
|
1956
|
+
/** True if the run requested a continuation. Caller is responsible for triggering the continuation. */
|
|
1957
|
+
continuation: boolean;
|
|
1958
|
+
/** True if the run paused at a tool-approval checkpoint. */
|
|
1959
|
+
checkpointed: boolean;
|
|
1960
|
+
contextTokens: number;
|
|
1961
|
+
contextWindow: number;
|
|
1962
|
+
}
|
|
1963
|
+
declare const runConversationTurn: (opts: RunConversationTurnOpts) => Promise<RunConversationTurnResult>;
|
|
1964
|
+
|
|
1965
|
+
export { type ActiveConversationRun, type ActiveSubagentRun, type AgentFrontmatter, AgentHarness, type AgentIdentity, type AgentLimitsConfig, type AgentModelConfig, AgentOrchestrator, type ApprovalEventItem, type ArchivedToolResult$1 as ArchivedToolResult, type BashConfig, BashEnvironmentManager, type BashExecutionLimits, type BuiltInToolToggles, CALLBACK_LOCK_STALE_MS, type CompactMessagesOptions, type CompactResult, type CompactionConfig, type ContinuationHooks, type Conversation, type ConversationCreateInit, type ConversationState, type ConversationStatusSnapshot, type ConversationStore, type ConversationSummary, type CreateSkillToolsOptions, type CronJobConfig, DEFAULT_AGENT_DESCRIPTION, DEFAULT_AGENT_NAME, DEFAULT_MAX_STEPS, DEFAULT_MODEL_NAME, DEFAULT_MODEL_PROVIDER, DEFAULT_TEMPERATURE, DEFAULT_TIMEOUT, type DefaultAgentDefinitionOptions, type EventSink, type ExecuteTurnResult, type HarnessOptions, type HarnessRunOutput, type HistorySource, InMemoryConversationStore, InMemoryEngine, InMemoryStateStore, type IsolateBinding, type IsolateConfig, LocalMcpBridge, LocalUploadStore, MAX_CONCURRENT_SUBAGENTS, MAX_CONTINUATION_COUNT, MAX_SUBAGENT_CALLBACK_COUNT, MAX_SUBAGENT_NESTING, type MainMemory, type McpConfig, type MemoryConfig, type MemoryStore, type MessagingChannelConfig, type ModelProviderFactory, type NetworkConfig, OPENAI_CODEX_CLIENT_ID, type OpenAICodexAuthConfig, type OpenAICodexDeviceAuthRequest, type OpenAICodexSession, type OrchestratorHooks, type OrchestratorOptions, type OtlpConfig, type OtlpOption, PONCHO_UPLOAD_SCHEME, type ParsedAgent, type PendingSubagentApproval, type PendingSubagentResult, type PendingToolCall, type PonchoConfig, PonchoFsAdapter, PostgresEngine, type ProviderConfig, type Recurrence, type RecurrenceType, type Reminder, type ReminderCreateInput, type ReminderStatus, type ReminderStore, type RemoteMcpServerConfig, type RunConversationTurnOpts, type RunConversationTurnResult, type RunOutcome, type RunRequest, type RuntimeRenderContext, S3UploadStore, STALE_SUBAGENT_THRESHOLD_MS, STORAGE_SCHEMA_VERSION, type SecretsStore, type SkillContextEntry, type SkillMetadata, type SkillSource, SqliteEngine, type StateConfig, type StateProviderName, type StateStore, type StorageConfig, type StorageEngine, type StorageFactoryOptions, type StorageProvider, type StoredApproval, type SubagentManager, type SubagentResult, type SubagentSpawnResult, type SubagentSummary, TOOL_RESULT_ARCHIVE_PARAM, type TelemetryConfig, TelemetryEmitter, type TenantTokenPayload, type ToolAccess, type ToolCall, ToolDispatcher, type ToolExecutionResult, type TurnDraftState, type TurnResultMetadata, type TurnSection, type UploadStore, type UploadsConfig, VFS_SCHEME, VercelBlobUploadStore, type VfsDirEntry, type VfsStat, applyTurnMetadata, buildAgentDirectoryName, buildApprovalCheckpoints, buildAssistantMetadata, buildSkillContextWindow, buildToolCompletedText, cloneSections, compactMessages, completeOpenAICodexDeviceAuth, computeNextOccurrence, createBashTool, createConversationStore, createConversationStoreFromEngine, createDefaultTools, createDeleteDirectoryTool, createDeleteTool, createEditTool, createMemoryStore, createMemoryStoreFromEngine, createMemoryTools, createModelProvider, createReminderStore, createReminderStoreFromEngine, createReminderTools, createSearchTools, createSecretsStore, createSkillTools, createStateStore, createStorageEngine, createSubagentTools, createTodoStoreFromEngine, createTurnDraftState, createUploadStore, createWriteTool, defaultAgentDefinition, deleteOpenAICodexSession, deriveUploadKey, ensureAgentIdentity, estimateTokens, estimateTotalTokens, executeConversationTurn, findSafeSplitPoint, flushTurnDraft, generateAgentId, getAgentStoreDirectory, getModelContextWindow, getOpenAICodexAccessToken, getOpenAICodexAuthFilePath, getOpenAICodexRequiredScopes, getPonchoStoreRoot, isMessageArray, jsonSchemaToZod, loadCanonicalHistory, loadPonchoConfig, loadRunHistory, loadSkillContext, loadSkillInstructions, loadSkillMetadata, loadVfsSkillMetadata, mergeSkills, normalizeApprovalCheckpoint, normalizeOtlp, normalizeScriptPolicyPath, parseAgentFile, parseAgentMarkdown, parseSkillFrontmatter, ponchoDocsTool, readOpenAICodexSession, readSkillResource, recordStandardTurnEvent, renderAgentPrompt, resolveAgentIdentity, resolveCompactionConfig, resolveEnv, resolveMemoryConfig, resolveRunRequest, resolveSkillDirs, resolveStateConfig, runConversationTurn, slugifyStorageComponent, startOpenAICodexDeviceAuth, verifyTenantToken, withToolResultArchiveParam, writeOpenAICodexSession };
|
package/dist/index.js
CHANGED
|
@@ -565,6 +565,53 @@ var loadPonchoConfig = async (workingDir) => {
|
|
|
565
565
|
}
|
|
566
566
|
};
|
|
567
567
|
|
|
568
|
+
// src/default-agent.ts
|
|
569
|
+
import { randomBytes } from "crypto";
|
|
570
|
+
var DEFAULT_AGENT_NAME = "agent";
|
|
571
|
+
var DEFAULT_AGENT_DESCRIPTION = "A helpful Poncho assistant";
|
|
572
|
+
var DEFAULT_MODEL_PROVIDER = "anthropic";
|
|
573
|
+
var DEFAULT_MODEL_NAME = "claude-opus-4-5";
|
|
574
|
+
var DEFAULT_TEMPERATURE = 0.2;
|
|
575
|
+
var DEFAULT_MAX_STEPS = 20;
|
|
576
|
+
var DEFAULT_TIMEOUT = 300;
|
|
577
|
+
var defaultAgentDefinition = (opts = {}) => {
|
|
578
|
+
const name = opts.name ?? DEFAULT_AGENT_NAME;
|
|
579
|
+
const id = opts.id ?? `agent_${randomBytes(16).toString("hex")}`;
|
|
580
|
+
const description = opts.description ?? DEFAULT_AGENT_DESCRIPTION;
|
|
581
|
+
const modelProvider = opts.modelProvider ?? DEFAULT_MODEL_PROVIDER;
|
|
582
|
+
const modelName = opts.modelName ?? DEFAULT_MODEL_NAME;
|
|
583
|
+
const temperature = opts.temperature ?? DEFAULT_TEMPERATURE;
|
|
584
|
+
const maxSteps = opts.maxSteps ?? DEFAULT_MAX_STEPS;
|
|
585
|
+
const timeout = opts.timeout ?? DEFAULT_TIMEOUT;
|
|
586
|
+
return `---
|
|
587
|
+
name: ${name}
|
|
588
|
+
id: ${id}
|
|
589
|
+
description: ${description}
|
|
590
|
+
model:
|
|
591
|
+
provider: ${modelProvider}
|
|
592
|
+
name: ${modelName}
|
|
593
|
+
temperature: ${temperature}
|
|
594
|
+
limits:
|
|
595
|
+
maxSteps: ${maxSteps}
|
|
596
|
+
timeout: ${timeout}
|
|
597
|
+
---
|
|
598
|
+
|
|
599
|
+
# {{name}}
|
|
600
|
+
|
|
601
|
+
You are **{{name}}**, a helpful assistant built with Poncho.
|
|
602
|
+
|
|
603
|
+
Working directory: {{runtime.workingDir}}
|
|
604
|
+
Environment: {{runtime.environment}}
|
|
605
|
+
|
|
606
|
+
## Task Guidance
|
|
607
|
+
|
|
608
|
+
- Use tools when needed
|
|
609
|
+
- Explain your reasoning clearly
|
|
610
|
+
- Ask clarifying questions when requirements are ambiguous
|
|
611
|
+
- Never claim a file/tool change unless the corresponding tool call actually succeeded
|
|
612
|
+
`;
|
|
613
|
+
};
|
|
614
|
+
|
|
568
615
|
// src/default-tools.ts
|
|
569
616
|
import { mkdir, readdir, readFile as readFile3, rm, unlink, writeFile as writeFile2 } from "fs/promises";
|
|
570
617
|
import { dirname, resolve as resolve4, sep } from "path";
|
|
@@ -5562,14 +5609,14 @@ var createTodoTools = (store) => {
|
|
|
5562
5609
|
};
|
|
5563
5610
|
|
|
5564
5611
|
// src/secrets-store.ts
|
|
5565
|
-
import { createCipheriv, createDecipheriv, randomBytes, createHash as createHash3 } from "crypto";
|
|
5612
|
+
import { createCipheriv, createDecipheriv, randomBytes as randomBytes2, createHash as createHash3 } from "crypto";
|
|
5566
5613
|
import { mkdir as mkdir3, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
|
|
5567
5614
|
import { dirname as dirname3, resolve as resolve7 } from "path";
|
|
5568
5615
|
function deriveKey(signingKey) {
|
|
5569
5616
|
return createHash3("sha256").update("poncho-secrets-v1:" + signingKey).digest();
|
|
5570
5617
|
}
|
|
5571
5618
|
function encrypt(plaintext, key) {
|
|
5572
|
-
const iv =
|
|
5619
|
+
const iv = randomBytes2(12);
|
|
5573
5620
|
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
5574
5621
|
const ct = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
5575
5622
|
const tag = cipher.getAuthTag();
|
|
@@ -9654,6 +9701,10 @@ var AgentHarness = class _AgentHarness {
|
|
|
9654
9701
|
listSkills() {
|
|
9655
9702
|
return this.loadedSkills.map((s) => ({ name: s.name, description: s.description }));
|
|
9656
9703
|
}
|
|
9704
|
+
async listSkillsForTenant(tenantId) {
|
|
9705
|
+
const skills = await this.getSkillsForTenant(tenantId);
|
|
9706
|
+
return skills.map((s) => ({ name: s.name, description: s.description }));
|
|
9707
|
+
}
|
|
9657
9708
|
/**
|
|
9658
9709
|
* Wraps the run() generator with an OTel root span (invoke_agent) so all
|
|
9659
9710
|
* child spans (LLM calls via AI SDK, tool execution) group under one trace.
|
|
@@ -12991,6 +13042,311 @@ ${resultBody}`,
|
|
|
12991
13042
|
}
|
|
12992
13043
|
};
|
|
12993
13044
|
|
|
13045
|
+
// src/orchestrator/run-conversation-turn.ts
|
|
13046
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
13047
|
+
import { createLogger as createLogger7 } from "@poncho-ai/sdk";
|
|
13048
|
+
var log = createLogger7("orchestrator");
|
|
13049
|
+
var runConversationTurn = async (opts) => {
|
|
13050
|
+
const conversation = await opts.conversationStore.getWithArchive(opts.conversationId);
|
|
13051
|
+
if (!conversation) {
|
|
13052
|
+
throw new Error(`Conversation not found: ${opts.conversationId}`);
|
|
13053
|
+
}
|
|
13054
|
+
const canonicalHistory = resolveRunRequest(conversation, {
|
|
13055
|
+
conversationId: opts.conversationId,
|
|
13056
|
+
messages: conversation.messages
|
|
13057
|
+
});
|
|
13058
|
+
const shouldRebuildCanonical = canonicalHistory.shouldRebuildCanonical;
|
|
13059
|
+
const harnessMessages = [...canonicalHistory.messages];
|
|
13060
|
+
const historyMessages = [...conversation.messages];
|
|
13061
|
+
const preRunMessages = [...conversation.messages];
|
|
13062
|
+
let userContent = opts.task;
|
|
13063
|
+
if (opts.files && opts.files.length > 0 && opts.harness.uploadStore) {
|
|
13064
|
+
const uploadedParts = await Promise.all(
|
|
13065
|
+
opts.files.map(async (f) => {
|
|
13066
|
+
const buf = Buffer.from(f.data, "base64");
|
|
13067
|
+
const key = deriveUploadKey(buf, f.mediaType);
|
|
13068
|
+
const ref = await opts.harness.uploadStore.put(key, buf, f.mediaType);
|
|
13069
|
+
return {
|
|
13070
|
+
type: "file",
|
|
13071
|
+
data: ref,
|
|
13072
|
+
mediaType: f.mediaType,
|
|
13073
|
+
filename: f.filename
|
|
13074
|
+
};
|
|
13075
|
+
})
|
|
13076
|
+
);
|
|
13077
|
+
userContent = [
|
|
13078
|
+
{ type: "text", text: opts.task },
|
|
13079
|
+
...uploadedParts
|
|
13080
|
+
];
|
|
13081
|
+
}
|
|
13082
|
+
const turnTimestamp = Date.now();
|
|
13083
|
+
const userMessage = {
|
|
13084
|
+
role: "user",
|
|
13085
|
+
content: userContent,
|
|
13086
|
+
metadata: { id: randomUUID6(), timestamp: turnTimestamp }
|
|
13087
|
+
};
|
|
13088
|
+
const assistantId = randomUUID6();
|
|
13089
|
+
const draft = createTurnDraftState();
|
|
13090
|
+
let latestRunId = conversation.runtimeRunId ?? "";
|
|
13091
|
+
let runCancelled = false;
|
|
13092
|
+
let runContinuationMessages;
|
|
13093
|
+
let cancelHarnessMessages;
|
|
13094
|
+
let checkpointedRun = false;
|
|
13095
|
+
const buildMessages = () => {
|
|
13096
|
+
const draftSections = cloneSections(draft.sections);
|
|
13097
|
+
if (draft.currentTools.length > 0) {
|
|
13098
|
+
draftSections.push({ type: "tools", content: [...draft.currentTools] });
|
|
13099
|
+
}
|
|
13100
|
+
if (draft.currentText.length > 0) {
|
|
13101
|
+
draftSections.push({ type: "text", content: draft.currentText });
|
|
13102
|
+
}
|
|
13103
|
+
const userTurn = [userMessage];
|
|
13104
|
+
const hasDraftContent = draft.assistantResponse.length > 0 || draft.toolTimeline.length > 0 || draftSections.length > 0;
|
|
13105
|
+
if (!hasDraftContent) {
|
|
13106
|
+
return [...historyMessages, ...userTurn];
|
|
13107
|
+
}
|
|
13108
|
+
return [
|
|
13109
|
+
...historyMessages,
|
|
13110
|
+
...userTurn,
|
|
13111
|
+
{
|
|
13112
|
+
role: "assistant",
|
|
13113
|
+
content: draft.assistantResponse,
|
|
13114
|
+
metadata: buildAssistantMetadata(draft, draftSections, {
|
|
13115
|
+
id: assistantId,
|
|
13116
|
+
timestamp: turnTimestamp
|
|
13117
|
+
})
|
|
13118
|
+
}
|
|
13119
|
+
];
|
|
13120
|
+
};
|
|
13121
|
+
const persistDraft = async () => {
|
|
13122
|
+
if (draft.assistantResponse.length === 0 && draft.toolTimeline.length === 0) return;
|
|
13123
|
+
conversation.messages = buildMessages();
|
|
13124
|
+
conversation.updatedAt = Date.now();
|
|
13125
|
+
await opts.conversationStore.update(conversation);
|
|
13126
|
+
};
|
|
13127
|
+
conversation.messages = [...historyMessages, userMessage];
|
|
13128
|
+
conversation.subagentCallbackCount = 0;
|
|
13129
|
+
conversation._continuationCount = void 0;
|
|
13130
|
+
conversation.updatedAt = Date.now();
|
|
13131
|
+
opts.conversationStore.update(conversation).catch((err) => {
|
|
13132
|
+
log.error(
|
|
13133
|
+
`failed to persist user turn: ${err instanceof Error ? err.message : String(err)}`
|
|
13134
|
+
);
|
|
13135
|
+
});
|
|
13136
|
+
try {
|
|
13137
|
+
const execution = await executeConversationTurn({
|
|
13138
|
+
harness: opts.harness,
|
|
13139
|
+
runInput: {
|
|
13140
|
+
task: opts.task,
|
|
13141
|
+
conversationId: opts.conversationId,
|
|
13142
|
+
tenantId: opts.tenantId ?? void 0,
|
|
13143
|
+
parameters: withToolResultArchiveParam(
|
|
13144
|
+
{
|
|
13145
|
+
...opts.parameters ?? {},
|
|
13146
|
+
__activeConversationId: opts.conversationId,
|
|
13147
|
+
__ownerId: conversation.ownerId
|
|
13148
|
+
},
|
|
13149
|
+
conversation
|
|
13150
|
+
),
|
|
13151
|
+
messages: harnessMessages,
|
|
13152
|
+
files: opts.files && opts.files.length > 0 ? opts.files : void 0,
|
|
13153
|
+
abortSignal: opts.abortSignal
|
|
13154
|
+
},
|
|
13155
|
+
initialContextTokens: conversation.contextTokens ?? 0,
|
|
13156
|
+
initialContextWindow: conversation.contextWindow ?? 0,
|
|
13157
|
+
onEvent: async (event, eventDraft) => {
|
|
13158
|
+
draft.assistantResponse = eventDraft.assistantResponse;
|
|
13159
|
+
draft.toolTimeline = eventDraft.toolTimeline;
|
|
13160
|
+
draft.sections = eventDraft.sections;
|
|
13161
|
+
draft.currentTools = eventDraft.currentTools;
|
|
13162
|
+
draft.currentText = eventDraft.currentText;
|
|
13163
|
+
if (event.type === "run:started") {
|
|
13164
|
+
latestRunId = event.runId;
|
|
13165
|
+
}
|
|
13166
|
+
if (event.type === "run:cancelled") {
|
|
13167
|
+
runCancelled = true;
|
|
13168
|
+
if (event.messages) cancelHarnessMessages = event.messages;
|
|
13169
|
+
}
|
|
13170
|
+
if (event.type === "compaction:completed") {
|
|
13171
|
+
if (event.compactedMessages) {
|
|
13172
|
+
historyMessages.length = 0;
|
|
13173
|
+
historyMessages.push(...event.compactedMessages);
|
|
13174
|
+
const preservedFromHistory = historyMessages.length - 1;
|
|
13175
|
+
const removedCount = preRunMessages.length - Math.max(0, preservedFromHistory);
|
|
13176
|
+
const existingHistory = conversation.compactedHistory ?? [];
|
|
13177
|
+
conversation.compactedHistory = [
|
|
13178
|
+
...existingHistory,
|
|
13179
|
+
...preRunMessages.slice(0, removedCount)
|
|
13180
|
+
];
|
|
13181
|
+
}
|
|
13182
|
+
}
|
|
13183
|
+
if (event.type === "step:completed") {
|
|
13184
|
+
await persistDraft();
|
|
13185
|
+
}
|
|
13186
|
+
if (event.type === "tool:approval:required") {
|
|
13187
|
+
const toolText = `- approval required \`${event.tool}\``;
|
|
13188
|
+
draft.toolTimeline.push(toolText);
|
|
13189
|
+
draft.currentTools.push(toolText);
|
|
13190
|
+
const existing = Array.isArray(conversation.pendingApprovals) ? conversation.pendingApprovals : [];
|
|
13191
|
+
if (!existing.some((a) => a.approvalId === event.approvalId)) {
|
|
13192
|
+
conversation.pendingApprovals = [
|
|
13193
|
+
...existing,
|
|
13194
|
+
{
|
|
13195
|
+
approvalId: event.approvalId,
|
|
13196
|
+
runId: latestRunId || conversation.runtimeRunId || "",
|
|
13197
|
+
tool: event.tool,
|
|
13198
|
+
toolCallId: void 0,
|
|
13199
|
+
input: event.input ?? {},
|
|
13200
|
+
checkpointMessages: void 0,
|
|
13201
|
+
baseMessageCount: historyMessages.length,
|
|
13202
|
+
pendingToolCalls: []
|
|
13203
|
+
}
|
|
13204
|
+
];
|
|
13205
|
+
conversation.updatedAt = Date.now();
|
|
13206
|
+
await opts.conversationStore.update(conversation);
|
|
13207
|
+
}
|
|
13208
|
+
await persistDraft();
|
|
13209
|
+
}
|
|
13210
|
+
if (event.type === "tool:approval:checkpoint") {
|
|
13211
|
+
conversation.messages = buildMessages();
|
|
13212
|
+
conversation.pendingApprovals = buildApprovalCheckpoints({
|
|
13213
|
+
approvals: event.approvals,
|
|
13214
|
+
runId: latestRunId,
|
|
13215
|
+
checkpointMessages: event.checkpointMessages,
|
|
13216
|
+
baseMessageCount: historyMessages.length,
|
|
13217
|
+
pendingToolCalls: event.pendingToolCalls
|
|
13218
|
+
});
|
|
13219
|
+
conversation._toolResultArchive = opts.harness.getToolResultArchive(
|
|
13220
|
+
opts.conversationId
|
|
13221
|
+
);
|
|
13222
|
+
conversation.updatedAt = Date.now();
|
|
13223
|
+
await opts.conversationStore.update(conversation);
|
|
13224
|
+
checkpointedRun = true;
|
|
13225
|
+
}
|
|
13226
|
+
if (event.type === "run:completed") {
|
|
13227
|
+
if (event.result.continuation && event.result.continuationMessages) {
|
|
13228
|
+
runContinuationMessages = event.result.continuationMessages;
|
|
13229
|
+
conversation.messages = buildMessages();
|
|
13230
|
+
conversation._continuationMessages = runContinuationMessages;
|
|
13231
|
+
conversation._harnessMessages = runContinuationMessages;
|
|
13232
|
+
conversation._toolResultArchive = opts.harness.getToolResultArchive(
|
|
13233
|
+
opts.conversationId
|
|
13234
|
+
);
|
|
13235
|
+
conversation.runtimeRunId = latestRunId || conversation.runtimeRunId;
|
|
13236
|
+
if (!checkpointedRun) {
|
|
13237
|
+
conversation.pendingApprovals = [];
|
|
13238
|
+
}
|
|
13239
|
+
if ((event.result.contextTokens ?? 0) > 0) {
|
|
13240
|
+
conversation.contextTokens = event.result.contextTokens;
|
|
13241
|
+
}
|
|
13242
|
+
if ((event.result.contextWindow ?? 0) > 0) {
|
|
13243
|
+
conversation.contextWindow = event.result.contextWindow;
|
|
13244
|
+
}
|
|
13245
|
+
conversation.updatedAt = Date.now();
|
|
13246
|
+
await opts.conversationStore.update(conversation);
|
|
13247
|
+
}
|
|
13248
|
+
}
|
|
13249
|
+
if (opts.onEvent) {
|
|
13250
|
+
await opts.onEvent(event);
|
|
13251
|
+
}
|
|
13252
|
+
}
|
|
13253
|
+
});
|
|
13254
|
+
flushTurnDraft(draft);
|
|
13255
|
+
latestRunId = execution.latestRunId || latestRunId;
|
|
13256
|
+
if (!checkpointedRun && !runContinuationMessages) {
|
|
13257
|
+
conversation.messages = buildMessages();
|
|
13258
|
+
applyTurnMetadata(
|
|
13259
|
+
conversation,
|
|
13260
|
+
{
|
|
13261
|
+
latestRunId,
|
|
13262
|
+
contextTokens: execution.runContextTokens,
|
|
13263
|
+
contextWindow: execution.runContextWindow,
|
|
13264
|
+
harnessMessages: execution.runHarnessMessages,
|
|
13265
|
+
toolResultArchive: opts.harness.getToolResultArchive(opts.conversationId)
|
|
13266
|
+
},
|
|
13267
|
+
{ shouldRebuildCanonical }
|
|
13268
|
+
);
|
|
13269
|
+
await opts.conversationStore.update(conversation);
|
|
13270
|
+
}
|
|
13271
|
+
return {
|
|
13272
|
+
latestRunId,
|
|
13273
|
+
cancelled: runCancelled,
|
|
13274
|
+
errored: false,
|
|
13275
|
+
continuation: !!runContinuationMessages,
|
|
13276
|
+
checkpointed: checkpointedRun,
|
|
13277
|
+
contextTokens: execution.runContextTokens,
|
|
13278
|
+
contextWindow: execution.runContextWindow
|
|
13279
|
+
};
|
|
13280
|
+
} catch (error) {
|
|
13281
|
+
flushTurnDraft(draft);
|
|
13282
|
+
const aborted = opts.abortSignal?.aborted === true;
|
|
13283
|
+
if (aborted || runCancelled) {
|
|
13284
|
+
if (draft.assistantResponse.length > 0 || draft.toolTimeline.length > 0 || draft.sections.length > 0) {
|
|
13285
|
+
conversation.messages = buildMessages();
|
|
13286
|
+
applyTurnMetadata(
|
|
13287
|
+
conversation,
|
|
13288
|
+
{
|
|
13289
|
+
latestRunId,
|
|
13290
|
+
contextTokens: 0,
|
|
13291
|
+
contextWindow: 0,
|
|
13292
|
+
harnessMessages: cancelHarnessMessages,
|
|
13293
|
+
toolResultArchive: opts.harness.getToolResultArchive(opts.conversationId)
|
|
13294
|
+
},
|
|
13295
|
+
{ shouldRebuildCanonical: true }
|
|
13296
|
+
);
|
|
13297
|
+
await opts.conversationStore.update(conversation);
|
|
13298
|
+
}
|
|
13299
|
+
if (!checkpointedRun) {
|
|
13300
|
+
const fresh = await opts.conversationStore.get(opts.conversationId);
|
|
13301
|
+
if (fresh && Array.isArray(fresh.pendingApprovals) && fresh.pendingApprovals.length > 0) {
|
|
13302
|
+
fresh.pendingApprovals = [];
|
|
13303
|
+
await opts.conversationStore.update(fresh);
|
|
13304
|
+
}
|
|
13305
|
+
}
|
|
13306
|
+
return {
|
|
13307
|
+
latestRunId,
|
|
13308
|
+
cancelled: true,
|
|
13309
|
+
errored: false,
|
|
13310
|
+
continuation: false,
|
|
13311
|
+
checkpointed: checkpointedRun,
|
|
13312
|
+
contextTokens: 0,
|
|
13313
|
+
contextWindow: 0
|
|
13314
|
+
};
|
|
13315
|
+
}
|
|
13316
|
+
const errorEvent = {
|
|
13317
|
+
type: "run:error",
|
|
13318
|
+
runId: latestRunId || "run_unknown",
|
|
13319
|
+
error: {
|
|
13320
|
+
code: "RUN_ERROR",
|
|
13321
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
13322
|
+
}
|
|
13323
|
+
};
|
|
13324
|
+
if (opts.onEvent) {
|
|
13325
|
+
try {
|
|
13326
|
+
await opts.onEvent(errorEvent);
|
|
13327
|
+
} catch (hookErr) {
|
|
13328
|
+
log.error(
|
|
13329
|
+
`onEvent threw on run:error: ${hookErr instanceof Error ? hookErr.message : String(hookErr)}`
|
|
13330
|
+
);
|
|
13331
|
+
}
|
|
13332
|
+
}
|
|
13333
|
+
if (draft.assistantResponse.length > 0 || draft.toolTimeline.length > 0 || draft.sections.length > 0) {
|
|
13334
|
+
conversation.messages = buildMessages();
|
|
13335
|
+
conversation.updatedAt = Date.now();
|
|
13336
|
+
await opts.conversationStore.update(conversation);
|
|
13337
|
+
}
|
|
13338
|
+
return {
|
|
13339
|
+
latestRunId,
|
|
13340
|
+
cancelled: false,
|
|
13341
|
+
errored: true,
|
|
13342
|
+
continuation: false,
|
|
13343
|
+
checkpointed: checkpointedRun,
|
|
13344
|
+
contextTokens: 0,
|
|
13345
|
+
contextWindow: 0
|
|
13346
|
+
};
|
|
13347
|
+
}
|
|
13348
|
+
};
|
|
13349
|
+
|
|
12994
13350
|
// src/index.ts
|
|
12995
13351
|
import { defineTool as defineTool13 } from "@poncho-ai/sdk";
|
|
12996
13352
|
export {
|
|
@@ -12998,6 +13354,13 @@ export {
|
|
|
12998
13354
|
AgentOrchestrator,
|
|
12999
13355
|
BashEnvironmentManager,
|
|
13000
13356
|
CALLBACK_LOCK_STALE_MS,
|
|
13357
|
+
DEFAULT_AGENT_DESCRIPTION,
|
|
13358
|
+
DEFAULT_AGENT_NAME,
|
|
13359
|
+
DEFAULT_MAX_STEPS,
|
|
13360
|
+
DEFAULT_MODEL_NAME,
|
|
13361
|
+
DEFAULT_MODEL_PROVIDER,
|
|
13362
|
+
DEFAULT_TEMPERATURE,
|
|
13363
|
+
DEFAULT_TIMEOUT,
|
|
13001
13364
|
InMemoryConversationStore,
|
|
13002
13365
|
InMemoryEngine,
|
|
13003
13366
|
InMemoryStateStore,
|
|
@@ -13054,6 +13417,7 @@ export {
|
|
|
13054
13417
|
createTurnDraftState,
|
|
13055
13418
|
createUploadStore,
|
|
13056
13419
|
createWriteTool,
|
|
13420
|
+
defaultAgentDefinition,
|
|
13057
13421
|
defineTool13 as defineTool,
|
|
13058
13422
|
deleteOpenAICodexSession,
|
|
13059
13423
|
deriveUploadKey,
|
|
@@ -13098,6 +13462,7 @@ export {
|
|
|
13098
13462
|
resolveRunRequest,
|
|
13099
13463
|
resolveSkillDirs,
|
|
13100
13464
|
resolveStateConfig,
|
|
13465
|
+
runConversationTurn,
|
|
13101
13466
|
slugifyStorageComponent,
|
|
13102
13467
|
startOpenAICodexDeviceAuth,
|
|
13103
13468
|
verifyTenantToken,
|
package/package.json
CHANGED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Canonical default agent definition.
|
|
3
|
+
//
|
|
4
|
+
// This is the same agent.md a fresh `poncho init` produces. The CLI's
|
|
5
|
+
// AGENT_TEMPLATE in `packages/cli/src/templates.ts` delegates to this helper
|
|
6
|
+
// so there is exactly one source of truth, and SDK consumers (PonchOS, custom
|
|
7
|
+
// servers, etc.) can pass the same default to `new AgentHarness({ agentDefinition: ... })`
|
|
8
|
+
// without hand-copying the template.
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
import { randomBytes } from "node:crypto";
|
|
12
|
+
|
|
13
|
+
export interface DefaultAgentDefinitionOptions {
|
|
14
|
+
/** Display name for the agent. Default: "agent". */
|
|
15
|
+
name?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Stable identifier embedded in the frontmatter. Default: a fresh
|
|
18
|
+
* `agent_<32hex>`. Note: when an injected `StorageEngine` is also passed
|
|
19
|
+
* to `AgentHarness`, the engine's `agentId` overrides this at runtime, so
|
|
20
|
+
* SDK consumers can leave it default.
|
|
21
|
+
*/
|
|
22
|
+
id?: string;
|
|
23
|
+
/** Frontmatter description. Default: "A helpful Poncho assistant". */
|
|
24
|
+
description?: string;
|
|
25
|
+
/** Model provider. Default: "anthropic". */
|
|
26
|
+
modelProvider?: "anthropic" | "openai" | "openai-codex";
|
|
27
|
+
/** Model name. Default: "claude-opus-4-5". */
|
|
28
|
+
modelName?: string;
|
|
29
|
+
/** Sampling temperature. Default: 0.2. */
|
|
30
|
+
temperature?: number;
|
|
31
|
+
/** Max tool-call steps per run. Default: 20. */
|
|
32
|
+
maxSteps?: number;
|
|
33
|
+
/** Hard timeout in seconds. Default: 300. */
|
|
34
|
+
timeout?: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const DEFAULT_AGENT_NAME = "agent";
|
|
38
|
+
export const DEFAULT_AGENT_DESCRIPTION = "A helpful Poncho assistant";
|
|
39
|
+
export const DEFAULT_MODEL_PROVIDER = "anthropic" as const;
|
|
40
|
+
export const DEFAULT_MODEL_NAME = "claude-opus-4-5";
|
|
41
|
+
export const DEFAULT_TEMPERATURE = 0.2;
|
|
42
|
+
export const DEFAULT_MAX_STEPS = 20;
|
|
43
|
+
export const DEFAULT_TIMEOUT = 300;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Returns the canonical default agent definition as a markdown string,
|
|
47
|
+
* ready to pass to `new AgentHarness({ agentDefinition })`. This is the
|
|
48
|
+
* exact same template `poncho init` writes to `AGENT.md`.
|
|
49
|
+
*/
|
|
50
|
+
export const defaultAgentDefinition = (
|
|
51
|
+
opts: DefaultAgentDefinitionOptions = {},
|
|
52
|
+
): string => {
|
|
53
|
+
const name = opts.name ?? DEFAULT_AGENT_NAME;
|
|
54
|
+
const id = opts.id ?? `agent_${randomBytes(16).toString("hex")}`;
|
|
55
|
+
const description = opts.description ?? DEFAULT_AGENT_DESCRIPTION;
|
|
56
|
+
const modelProvider = opts.modelProvider ?? DEFAULT_MODEL_PROVIDER;
|
|
57
|
+
const modelName = opts.modelName ?? DEFAULT_MODEL_NAME;
|
|
58
|
+
const temperature = opts.temperature ?? DEFAULT_TEMPERATURE;
|
|
59
|
+
const maxSteps = opts.maxSteps ?? DEFAULT_MAX_STEPS;
|
|
60
|
+
const timeout = opts.timeout ?? DEFAULT_TIMEOUT;
|
|
61
|
+
|
|
62
|
+
return `---
|
|
63
|
+
name: ${name}
|
|
64
|
+
id: ${id}
|
|
65
|
+
description: ${description}
|
|
66
|
+
model:
|
|
67
|
+
provider: ${modelProvider}
|
|
68
|
+
name: ${modelName}
|
|
69
|
+
temperature: ${temperature}
|
|
70
|
+
limits:
|
|
71
|
+
maxSteps: ${maxSteps}
|
|
72
|
+
timeout: ${timeout}
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
# {{name}}
|
|
76
|
+
|
|
77
|
+
You are **{{name}}**, a helpful assistant built with Poncho.
|
|
78
|
+
|
|
79
|
+
Working directory: {{runtime.workingDir}}
|
|
80
|
+
Environment: {{runtime.environment}}
|
|
81
|
+
|
|
82
|
+
## Task Guidance
|
|
83
|
+
|
|
84
|
+
- Use tools when needed
|
|
85
|
+
- Explain your reasoning clearly
|
|
86
|
+
- Ask clarifying questions when requirements are ambiguous
|
|
87
|
+
- Never claim a file/tool change unless the corresponding tool call actually succeeded
|
|
88
|
+
`;
|
|
89
|
+
};
|
package/src/harness.ts
CHANGED
|
@@ -1940,6 +1940,13 @@ export class AgentHarness {
|
|
|
1940
1940
|
return this.loadedSkills.map((s) => ({ name: s.name, description: s.description }));
|
|
1941
1941
|
}
|
|
1942
1942
|
|
|
1943
|
+
async listSkillsForTenant(
|
|
1944
|
+
tenantId: string | undefined | null,
|
|
1945
|
+
): Promise<Array<{ name: string; description: string }>> {
|
|
1946
|
+
const skills = await this.getSkillsForTenant(tenantId);
|
|
1947
|
+
return skills.map((s) => ({ name: s.name, description: s.description }));
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1943
1950
|
/**
|
|
1944
1951
|
* Wraps the run() generator with an OTel root span (invoke_agent) so all
|
|
1945
1952
|
* child spans (LLM calls via AI SDK, tool execution) group under one trace.
|
package/src/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ export * from "./agent-parser.js";
|
|
|
2
2
|
export * from "./agent-identity.js";
|
|
3
3
|
export * from "./compaction.js";
|
|
4
4
|
export * from "./config.js";
|
|
5
|
+
export * from "./default-agent.js";
|
|
5
6
|
export * from "./default-tools.js";
|
|
6
7
|
export * from "./harness.js";
|
|
7
8
|
export * from "./memory.js";
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// runConversationTurn — load-bearing helper that runs a single primary chat
|
|
3
|
+
// turn end-to-end against a ConversationStore: loads the conversation,
|
|
4
|
+
// persists the user message before the run, drives the model + tool loop
|
|
5
|
+
// via executeConversationTurn, periodically persists the in-flight assistant
|
|
6
|
+
// draft, handles approval checkpoints + continuations + cancellation, and
|
|
7
|
+
// finalises the conversation row on completion.
|
|
8
|
+
//
|
|
9
|
+
// This was extracted from packages/cli/src/index.ts (POST
|
|
10
|
+
// /api/conversations/:id/messages handler) so consumers other than the CLI
|
|
11
|
+
// (PonchOS, custom servers) can ship the *same* conversation lifecycle
|
|
12
|
+
// without copy-pasting hundreds of lines.
|
|
13
|
+
//
|
|
14
|
+
// Caller responsibilities (NOT done here):
|
|
15
|
+
// - auth / ownership checks
|
|
16
|
+
// - active-run dedup (one run at a time per conversation)
|
|
17
|
+
// - streaming events to a real client (use opts.onEvent)
|
|
18
|
+
// - triggering continuation runs after this returns continuation: true
|
|
19
|
+
// - conversation title inference (helper preserves existing title)
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
import { randomUUID } from "node:crypto";
|
|
23
|
+
import type { AgentEvent, FileInput, Message } from "@poncho-ai/sdk";
|
|
24
|
+
import { createLogger } from "@poncho-ai/sdk";
|
|
25
|
+
import type { AgentHarness } from "../harness.js";
|
|
26
|
+
import type { ConversationStore } from "../state.js";
|
|
27
|
+
import { deriveUploadKey } from "../upload-store.js";
|
|
28
|
+
import { withToolResultArchiveParam } from "./continuation.js";
|
|
29
|
+
import { resolveRunRequest } from "./history.js";
|
|
30
|
+
import {
|
|
31
|
+
applyTurnMetadata,
|
|
32
|
+
buildApprovalCheckpoints,
|
|
33
|
+
buildAssistantMetadata,
|
|
34
|
+
cloneSections,
|
|
35
|
+
createTurnDraftState,
|
|
36
|
+
executeConversationTurn,
|
|
37
|
+
flushTurnDraft,
|
|
38
|
+
} from "./turn.js";
|
|
39
|
+
|
|
40
|
+
const log = createLogger("orchestrator");
|
|
41
|
+
|
|
42
|
+
export interface RunConversationTurnOpts {
|
|
43
|
+
/** Initialised harness instance. */
|
|
44
|
+
harness: AgentHarness;
|
|
45
|
+
/** Conversation store backing the turn (typically `engine.conversations` from a StorageEngine). */
|
|
46
|
+
conversationStore: ConversationStore;
|
|
47
|
+
conversationId: string;
|
|
48
|
+
/** The user's new message text. Required (use `""` if you only want to attach files). */
|
|
49
|
+
task: string;
|
|
50
|
+
/**
|
|
51
|
+
* Optional file attachments (FileInput.data is base64 / data URI / https URL).
|
|
52
|
+
* Files are uploaded via `harness.uploadStore` first so the persisted user
|
|
53
|
+
* message references stable URLs instead of fat base64 blobs.
|
|
54
|
+
*/
|
|
55
|
+
files?: FileInput[];
|
|
56
|
+
/**
|
|
57
|
+
* Extra parameters merged into runInput.parameters. Use this for recall
|
|
58
|
+
* corpus, archive lookup keys, messaging metadata, etc. Do NOT include
|
|
59
|
+
* `__activeConversationId`, `__ownerId`, or the tool-result-archive — the
|
|
60
|
+
* helper sets those itself.
|
|
61
|
+
*/
|
|
62
|
+
parameters?: Record<string, unknown>;
|
|
63
|
+
abortSignal?: AbortSignal;
|
|
64
|
+
tenantId?: string | null;
|
|
65
|
+
/** Per-event hook — called for every AgentEvent yielded by the run, in order. */
|
|
66
|
+
onEvent?: (event: AgentEvent) => void | Promise<void>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface RunConversationTurnResult {
|
|
70
|
+
/** runId of the most recent run started during this turn. */
|
|
71
|
+
latestRunId: string;
|
|
72
|
+
/** True if the run was cancelled (via abortSignal or run:cancelled event). */
|
|
73
|
+
cancelled: boolean;
|
|
74
|
+
/** True if the run errored. The error has been emitted via onEvent as run:error. */
|
|
75
|
+
errored: boolean;
|
|
76
|
+
/** True if the run requested a continuation. Caller is responsible for triggering the continuation. */
|
|
77
|
+
continuation: boolean;
|
|
78
|
+
/** True if the run paused at a tool-approval checkpoint. */
|
|
79
|
+
checkpointed: boolean;
|
|
80
|
+
contextTokens: number;
|
|
81
|
+
contextWindow: number;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const runConversationTurn = async (
|
|
85
|
+
opts: RunConversationTurnOpts,
|
|
86
|
+
): Promise<RunConversationTurnResult> => {
|
|
87
|
+
const conversation = await opts.conversationStore.getWithArchive(opts.conversationId);
|
|
88
|
+
if (!conversation) {
|
|
89
|
+
throw new Error(`Conversation not found: ${opts.conversationId}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const canonicalHistory = resolveRunRequest(conversation, {
|
|
93
|
+
conversationId: opts.conversationId,
|
|
94
|
+
messages: conversation.messages,
|
|
95
|
+
});
|
|
96
|
+
const shouldRebuildCanonical = canonicalHistory.shouldRebuildCanonical;
|
|
97
|
+
const harnessMessages = [...canonicalHistory.messages];
|
|
98
|
+
const historyMessages = [...conversation.messages];
|
|
99
|
+
const preRunMessages = [...conversation.messages];
|
|
100
|
+
|
|
101
|
+
// Build user content — upload any files first so the persisted message
|
|
102
|
+
// carries stable refs instead of fat base64 blobs.
|
|
103
|
+
let userContent: Message["content"] = opts.task;
|
|
104
|
+
if (opts.files && opts.files.length > 0 && opts.harness.uploadStore) {
|
|
105
|
+
const uploadedParts = await Promise.all(
|
|
106
|
+
opts.files.map(async (f) => {
|
|
107
|
+
const buf = Buffer.from(f.data, "base64");
|
|
108
|
+
const key = deriveUploadKey(buf, f.mediaType);
|
|
109
|
+
const ref = await opts.harness.uploadStore!.put(key, buf, f.mediaType);
|
|
110
|
+
return {
|
|
111
|
+
type: "file" as const,
|
|
112
|
+
data: ref,
|
|
113
|
+
mediaType: f.mediaType,
|
|
114
|
+
filename: f.filename,
|
|
115
|
+
};
|
|
116
|
+
}),
|
|
117
|
+
);
|
|
118
|
+
userContent = [
|
|
119
|
+
{ type: "text" as const, text: opts.task },
|
|
120
|
+
...uploadedParts,
|
|
121
|
+
];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const turnTimestamp = Date.now();
|
|
125
|
+
const userMessage: Message = {
|
|
126
|
+
role: "user",
|
|
127
|
+
content: userContent,
|
|
128
|
+
metadata: { id: randomUUID(), timestamp: turnTimestamp },
|
|
129
|
+
};
|
|
130
|
+
const assistantId = randomUUID();
|
|
131
|
+
const draft = createTurnDraftState();
|
|
132
|
+
|
|
133
|
+
let latestRunId = conversation.runtimeRunId ?? "";
|
|
134
|
+
let runCancelled = false;
|
|
135
|
+
let runContinuationMessages: Message[] | undefined;
|
|
136
|
+
let cancelHarnessMessages: Message[] | undefined;
|
|
137
|
+
let checkpointedRun = false;
|
|
138
|
+
|
|
139
|
+
const buildMessages = (): Message[] => {
|
|
140
|
+
const draftSections = cloneSections(draft.sections);
|
|
141
|
+
if (draft.currentTools.length > 0) {
|
|
142
|
+
draftSections.push({ type: "tools", content: [...draft.currentTools] });
|
|
143
|
+
}
|
|
144
|
+
if (draft.currentText.length > 0) {
|
|
145
|
+
draftSections.push({ type: "text", content: draft.currentText });
|
|
146
|
+
}
|
|
147
|
+
const userTurn: Message[] = [userMessage];
|
|
148
|
+
const hasDraftContent =
|
|
149
|
+
draft.assistantResponse.length > 0 ||
|
|
150
|
+
draft.toolTimeline.length > 0 ||
|
|
151
|
+
draftSections.length > 0;
|
|
152
|
+
if (!hasDraftContent) {
|
|
153
|
+
return [...historyMessages, ...userTurn];
|
|
154
|
+
}
|
|
155
|
+
return [
|
|
156
|
+
...historyMessages,
|
|
157
|
+
...userTurn,
|
|
158
|
+
{
|
|
159
|
+
role: "assistant" as const,
|
|
160
|
+
content: draft.assistantResponse,
|
|
161
|
+
metadata: buildAssistantMetadata(draft, draftSections, {
|
|
162
|
+
id: assistantId,
|
|
163
|
+
timestamp: turnTimestamp,
|
|
164
|
+
}),
|
|
165
|
+
},
|
|
166
|
+
];
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const persistDraft = async (): Promise<void> => {
|
|
170
|
+
if (draft.assistantResponse.length === 0 && draft.toolTimeline.length === 0) return;
|
|
171
|
+
conversation.messages = buildMessages();
|
|
172
|
+
conversation.updatedAt = Date.now();
|
|
173
|
+
await opts.conversationStore.update(conversation);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// Persist the user turn immediately so a crash mid-run still records what
|
|
177
|
+
// the user said. Fire-and-forget — don't block the run.
|
|
178
|
+
conversation.messages = [...historyMessages, userMessage];
|
|
179
|
+
conversation.subagentCallbackCount = 0;
|
|
180
|
+
conversation._continuationCount = undefined;
|
|
181
|
+
conversation.updatedAt = Date.now();
|
|
182
|
+
opts.conversationStore.update(conversation).catch((err) => {
|
|
183
|
+
log.error(
|
|
184
|
+
`failed to persist user turn: ${err instanceof Error ? err.message : String(err)}`,
|
|
185
|
+
);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
const execution = await executeConversationTurn({
|
|
190
|
+
harness: opts.harness,
|
|
191
|
+
runInput: {
|
|
192
|
+
task: opts.task,
|
|
193
|
+
conversationId: opts.conversationId,
|
|
194
|
+
tenantId: opts.tenantId ?? undefined,
|
|
195
|
+
parameters: withToolResultArchiveParam(
|
|
196
|
+
{
|
|
197
|
+
...(opts.parameters ?? {}),
|
|
198
|
+
__activeConversationId: opts.conversationId,
|
|
199
|
+
__ownerId: conversation.ownerId,
|
|
200
|
+
},
|
|
201
|
+
conversation,
|
|
202
|
+
),
|
|
203
|
+
messages: harnessMessages,
|
|
204
|
+
files: opts.files && opts.files.length > 0 ? opts.files : undefined,
|
|
205
|
+
abortSignal: opts.abortSignal,
|
|
206
|
+
},
|
|
207
|
+
initialContextTokens: conversation.contextTokens ?? 0,
|
|
208
|
+
initialContextWindow: conversation.contextWindow ?? 0,
|
|
209
|
+
onEvent: async (event, eventDraft) => {
|
|
210
|
+
// Sync our outer draft from the executor's so persistDraft sees the latest state.
|
|
211
|
+
draft.assistantResponse = eventDraft.assistantResponse;
|
|
212
|
+
draft.toolTimeline = eventDraft.toolTimeline;
|
|
213
|
+
draft.sections = eventDraft.sections;
|
|
214
|
+
draft.currentTools = eventDraft.currentTools;
|
|
215
|
+
draft.currentText = eventDraft.currentText;
|
|
216
|
+
|
|
217
|
+
if (event.type === "run:started") {
|
|
218
|
+
latestRunId = event.runId;
|
|
219
|
+
}
|
|
220
|
+
if (event.type === "run:cancelled") {
|
|
221
|
+
runCancelled = true;
|
|
222
|
+
if (event.messages) cancelHarnessMessages = event.messages;
|
|
223
|
+
}
|
|
224
|
+
if (event.type === "compaction:completed") {
|
|
225
|
+
if (event.compactedMessages) {
|
|
226
|
+
historyMessages.length = 0;
|
|
227
|
+
historyMessages.push(...event.compactedMessages);
|
|
228
|
+
const preservedFromHistory = historyMessages.length - 1;
|
|
229
|
+
const removedCount =
|
|
230
|
+
preRunMessages.length - Math.max(0, preservedFromHistory);
|
|
231
|
+
const existingHistory = conversation.compactedHistory ?? [];
|
|
232
|
+
conversation.compactedHistory = [
|
|
233
|
+
...existingHistory,
|
|
234
|
+
...preRunMessages.slice(0, removedCount),
|
|
235
|
+
];
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (event.type === "step:completed") {
|
|
239
|
+
await persistDraft();
|
|
240
|
+
}
|
|
241
|
+
if (event.type === "tool:approval:required") {
|
|
242
|
+
const toolText = `- approval required \`${event.tool}\``;
|
|
243
|
+
draft.toolTimeline.push(toolText);
|
|
244
|
+
draft.currentTools.push(toolText);
|
|
245
|
+
const existing = Array.isArray(conversation.pendingApprovals)
|
|
246
|
+
? conversation.pendingApprovals
|
|
247
|
+
: [];
|
|
248
|
+
if (!existing.some((a) => a.approvalId === event.approvalId)) {
|
|
249
|
+
conversation.pendingApprovals = [
|
|
250
|
+
...existing,
|
|
251
|
+
{
|
|
252
|
+
approvalId: event.approvalId,
|
|
253
|
+
runId: latestRunId || conversation.runtimeRunId || "",
|
|
254
|
+
tool: event.tool,
|
|
255
|
+
toolCallId: undefined,
|
|
256
|
+
input: (event.input ?? {}) as Record<string, unknown>,
|
|
257
|
+
checkpointMessages: undefined,
|
|
258
|
+
baseMessageCount: historyMessages.length,
|
|
259
|
+
pendingToolCalls: [],
|
|
260
|
+
},
|
|
261
|
+
];
|
|
262
|
+
conversation.updatedAt = Date.now();
|
|
263
|
+
await opts.conversationStore.update(conversation);
|
|
264
|
+
}
|
|
265
|
+
await persistDraft();
|
|
266
|
+
}
|
|
267
|
+
if (event.type === "tool:approval:checkpoint") {
|
|
268
|
+
conversation.messages = buildMessages();
|
|
269
|
+
conversation.pendingApprovals = buildApprovalCheckpoints({
|
|
270
|
+
approvals: event.approvals,
|
|
271
|
+
runId: latestRunId,
|
|
272
|
+
checkpointMessages: event.checkpointMessages,
|
|
273
|
+
baseMessageCount: historyMessages.length,
|
|
274
|
+
pendingToolCalls: event.pendingToolCalls,
|
|
275
|
+
});
|
|
276
|
+
conversation._toolResultArchive = opts.harness.getToolResultArchive(
|
|
277
|
+
opts.conversationId,
|
|
278
|
+
);
|
|
279
|
+
conversation.updatedAt = Date.now();
|
|
280
|
+
await opts.conversationStore.update(conversation);
|
|
281
|
+
checkpointedRun = true;
|
|
282
|
+
}
|
|
283
|
+
if (event.type === "run:completed") {
|
|
284
|
+
if (event.result.continuation && event.result.continuationMessages) {
|
|
285
|
+
runContinuationMessages = event.result.continuationMessages;
|
|
286
|
+
conversation.messages = buildMessages();
|
|
287
|
+
conversation._continuationMessages = runContinuationMessages;
|
|
288
|
+
conversation._harnessMessages = runContinuationMessages;
|
|
289
|
+
conversation._toolResultArchive = opts.harness.getToolResultArchive(
|
|
290
|
+
opts.conversationId,
|
|
291
|
+
);
|
|
292
|
+
conversation.runtimeRunId = latestRunId || conversation.runtimeRunId;
|
|
293
|
+
if (!checkpointedRun) {
|
|
294
|
+
conversation.pendingApprovals = [];
|
|
295
|
+
}
|
|
296
|
+
if ((event.result.contextTokens ?? 0) > 0) {
|
|
297
|
+
conversation.contextTokens = event.result.contextTokens!;
|
|
298
|
+
}
|
|
299
|
+
if ((event.result.contextWindow ?? 0) > 0) {
|
|
300
|
+
conversation.contextWindow = event.result.contextWindow!;
|
|
301
|
+
}
|
|
302
|
+
conversation.updatedAt = Date.now();
|
|
303
|
+
await opts.conversationStore.update(conversation);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (opts.onEvent) {
|
|
308
|
+
await opts.onEvent(event);
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
flushTurnDraft(draft);
|
|
314
|
+
latestRunId = execution.latestRunId || latestRunId;
|
|
315
|
+
|
|
316
|
+
if (!checkpointedRun && !runContinuationMessages) {
|
|
317
|
+
conversation.messages = buildMessages();
|
|
318
|
+
applyTurnMetadata(
|
|
319
|
+
conversation,
|
|
320
|
+
{
|
|
321
|
+
latestRunId,
|
|
322
|
+
contextTokens: execution.runContextTokens,
|
|
323
|
+
contextWindow: execution.runContextWindow,
|
|
324
|
+
harnessMessages: execution.runHarnessMessages,
|
|
325
|
+
toolResultArchive: opts.harness.getToolResultArchive(opts.conversationId),
|
|
326
|
+
},
|
|
327
|
+
{ shouldRebuildCanonical },
|
|
328
|
+
);
|
|
329
|
+
await opts.conversationStore.update(conversation);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
latestRunId,
|
|
334
|
+
cancelled: runCancelled,
|
|
335
|
+
errored: false,
|
|
336
|
+
continuation: !!runContinuationMessages,
|
|
337
|
+
checkpointed: checkpointedRun,
|
|
338
|
+
contextTokens: execution.runContextTokens,
|
|
339
|
+
contextWindow: execution.runContextWindow,
|
|
340
|
+
};
|
|
341
|
+
} catch (error) {
|
|
342
|
+
flushTurnDraft(draft);
|
|
343
|
+
const aborted = opts.abortSignal?.aborted === true;
|
|
344
|
+
if (aborted || runCancelled) {
|
|
345
|
+
if (
|
|
346
|
+
draft.assistantResponse.length > 0 ||
|
|
347
|
+
draft.toolTimeline.length > 0 ||
|
|
348
|
+
draft.sections.length > 0
|
|
349
|
+
) {
|
|
350
|
+
conversation.messages = buildMessages();
|
|
351
|
+
applyTurnMetadata(
|
|
352
|
+
conversation,
|
|
353
|
+
{
|
|
354
|
+
latestRunId,
|
|
355
|
+
contextTokens: 0,
|
|
356
|
+
contextWindow: 0,
|
|
357
|
+
harnessMessages: cancelHarnessMessages,
|
|
358
|
+
toolResultArchive: opts.harness.getToolResultArchive(opts.conversationId),
|
|
359
|
+
},
|
|
360
|
+
{ shouldRebuildCanonical: true },
|
|
361
|
+
);
|
|
362
|
+
await opts.conversationStore.update(conversation);
|
|
363
|
+
}
|
|
364
|
+
if (!checkpointedRun) {
|
|
365
|
+
// Clear any pending approvals — the run was cancelled, they're stale.
|
|
366
|
+
const fresh = await opts.conversationStore.get(opts.conversationId);
|
|
367
|
+
if (fresh && Array.isArray(fresh.pendingApprovals) && fresh.pendingApprovals.length > 0) {
|
|
368
|
+
fresh.pendingApprovals = [];
|
|
369
|
+
await opts.conversationStore.update(fresh);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return {
|
|
373
|
+
latestRunId,
|
|
374
|
+
cancelled: true,
|
|
375
|
+
errored: false,
|
|
376
|
+
continuation: false,
|
|
377
|
+
checkpointed: checkpointedRun,
|
|
378
|
+
contextTokens: 0,
|
|
379
|
+
contextWindow: 0,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Real error: emit run:error, persist whatever we have.
|
|
384
|
+
const errorEvent: AgentEvent = {
|
|
385
|
+
type: "run:error",
|
|
386
|
+
runId: latestRunId || "run_unknown",
|
|
387
|
+
error: {
|
|
388
|
+
code: "RUN_ERROR",
|
|
389
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
390
|
+
},
|
|
391
|
+
};
|
|
392
|
+
if (opts.onEvent) {
|
|
393
|
+
try {
|
|
394
|
+
await opts.onEvent(errorEvent);
|
|
395
|
+
} catch (hookErr) {
|
|
396
|
+
log.error(
|
|
397
|
+
`onEvent threw on run:error: ${hookErr instanceof Error ? hookErr.message : String(hookErr)}`,
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (
|
|
402
|
+
draft.assistantResponse.length > 0 ||
|
|
403
|
+
draft.toolTimeline.length > 0 ||
|
|
404
|
+
draft.sections.length > 0
|
|
405
|
+
) {
|
|
406
|
+
conversation.messages = buildMessages();
|
|
407
|
+
conversation.updatedAt = Date.now();
|
|
408
|
+
await opts.conversationStore.update(conversation);
|
|
409
|
+
}
|
|
410
|
+
return {
|
|
411
|
+
latestRunId,
|
|
412
|
+
cancelled: false,
|
|
413
|
+
errored: true,
|
|
414
|
+
continuation: false,
|
|
415
|
+
checkpointed: checkpointedRun,
|
|
416
|
+
contextTokens: 0,
|
|
417
|
+
contextWindow: 0,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
};
|