@code-yeongyu/senpi-agent-core 2026.9.30 → 2026.10.1-2

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.
Files changed (64) hide show
  1. package/README.md +49 -20
  2. package/dist/agent-loop.d.ts +27 -3
  3. package/dist/agent-loop.js +192 -56
  4. package/dist/agent.d.ts +21 -7
  5. package/dist/agent.js +38 -18
  6. package/dist/harness/execution/tools.d.ts +1 -1
  7. package/dist/harness/execution/tools.js +0 -2
  8. package/dist/harness/messages.js +1 -0
  9. package/dist/harness/pico3/bash.d.ts +10 -0
  10. package/dist/harness/pico3/bash.js +25 -0
  11. package/dist/harness/pico3/bounded.d.ts +21 -0
  12. package/dist/harness/pico3/bounded.js +95 -0
  13. package/dist/harness/pico3/chord.d.ts +41 -0
  14. package/dist/harness/pico3/chord.js +147 -0
  15. package/dist/harness/pico3/context.d.ts +16 -0
  16. package/dist/harness/pico3/context.js +88 -0
  17. package/dist/harness/pico3/harness.d.ts +198 -0
  18. package/dist/harness/pico3/harness.js +645 -0
  19. package/dist/harness/pico3/hooks.d.ts +10 -0
  20. package/dist/harness/pico3/hooks.js +33 -0
  21. package/dist/harness/pico3/index.d.ts +19 -0
  22. package/dist/harness/pico3/index.js +15 -0
  23. package/dist/harness/pico3/jsonl.d.ts +58 -0
  24. package/dist/harness/pico3/jsonl.js +322 -0
  25. package/dist/harness/pico3/kinds/collapse.d.ts +49 -0
  26. package/dist/harness/pico3/kinds/collapse.js +191 -0
  27. package/dist/harness/pico3/kinds/entries.d.ts +78 -0
  28. package/dist/harness/pico3/kinds/entries.js +13 -0
  29. package/dist/harness/pico3/kinds/frames.d.ts +9 -0
  30. package/dist/harness/pico3/kinds/frames.js +75 -0
  31. package/dist/harness/pico3/kinds/generation.d.ts +94 -0
  32. package/dist/harness/pico3/kinds/generation.js +504 -0
  33. package/dist/harness/pico3/kinds/job.d.ts +44 -0
  34. package/dist/harness/pico3/kinds/job.js +143 -0
  35. package/dist/harness/pico3/kinds/plugin.d.ts +15 -0
  36. package/dist/harness/pico3/kinds/plugin.js +35 -0
  37. package/dist/harness/pico3/kinds/post-tools.d.ts +22 -0
  38. package/dist/harness/pico3/kinds/post-tools.js +145 -0
  39. package/dist/harness/pico3/kinds/task-api.d.ts +3 -0
  40. package/dist/harness/pico3/kinds/task-api.js +45 -0
  41. package/dist/harness/pico3/kinds/tool.d.ts +43 -0
  42. package/dist/harness/pico3/kinds/tool.js +373 -0
  43. package/dist/harness/pico3/legacy-tracker.d.ts +11 -0
  44. package/dist/harness/pico3/legacy-tracker.js +39 -0
  45. package/dist/harness/pico3/membrane.d.ts +23 -0
  46. package/dist/harness/pico3/membrane.js +140 -0
  47. package/dist/harness/pico3/memory.d.ts +53 -0
  48. package/dist/harness/pico3/memory.js +265 -0
  49. package/dist/harness/pico3/scheduler.d.ts +49 -0
  50. package/dist/harness/pico3/scheduler.js +437 -0
  51. package/dist/harness/pico3/session.d.ts +264 -0
  52. package/dist/harness/pico3/session.js +1328 -0
  53. package/dist/harness/pico3/system.d.ts +126 -0
  54. package/dist/harness/pico3/system.js +244 -0
  55. package/dist/harness/pico3/types.d.ts +973 -0
  56. package/dist/harness/pico3/types.js +94 -0
  57. package/dist/harness/pico3/view.d.ts +34 -0
  58. package/dist/harness/pico3/view.js +404 -0
  59. package/dist/harness/runtime/drive/tool-placement.js +2 -27
  60. package/dist/harness/telemetry.d.ts +36 -36
  61. package/dist/harness/tools/image.js +1 -1
  62. package/dist/proxy.d.ts +2 -2
  63. package/dist/types.d.ts +111 -34
  64. package/package.json +9 -5
@@ -128,6 +128,12 @@ export declare const HARNESS_TELEMETRY_SCHEMA: {
128
128
  readonly kind: "root_or_external";
129
129
  };
130
130
  readonly startAttributes: {
131
+ readonly "pi.operation.kind": {
132
+ readonly type: "string";
133
+ readonly required: true;
134
+ readonly values: readonly ["run"];
135
+ readonly description: "Run operation kind";
136
+ };
131
137
  readonly "pi.session.id": {
132
138
  readonly type: "string";
133
139
  readonly required: true;
@@ -151,12 +157,6 @@ export declare const HARNESS_TELEMETRY_SCHEMA: {
151
157
  readonly required: true;
152
158
  readonly description: "Whether this invocation resumes durable work";
153
159
  };
154
- readonly "pi.operation.kind": {
155
- readonly type: "string";
156
- readonly required: true;
157
- readonly values: readonly ["run"];
158
- readonly description: "Run operation kind";
159
- };
160
160
  };
161
161
  readonly endAttributes: {
162
162
  readonly "pi.error.code": {
@@ -186,6 +186,12 @@ export declare const HARNESS_TELEMETRY_SCHEMA: {
186
186
  readonly kind: "root_or_external";
187
187
  };
188
188
  readonly startAttributes: {
189
+ readonly "pi.operation.kind": {
190
+ readonly type: "string";
191
+ readonly required: true;
192
+ readonly values: readonly ["compaction"];
193
+ readonly description: "Compaction operation kind";
194
+ };
189
195
  readonly "pi.session.id": {
190
196
  readonly type: "string";
191
197
  readonly required: true;
@@ -209,12 +215,6 @@ export declare const HARNESS_TELEMETRY_SCHEMA: {
209
215
  readonly required: true;
210
216
  readonly description: "Whether this invocation resumes durable work";
211
217
  };
212
- readonly "pi.operation.kind": {
213
- readonly type: "string";
214
- readonly required: true;
215
- readonly values: readonly ["compaction"];
216
- readonly description: "Compaction operation kind";
217
- };
218
218
  };
219
219
  readonly endAttributes: {
220
220
  readonly "pi.error.code": {
@@ -244,6 +244,12 @@ export declare const HARNESS_TELEMETRY_SCHEMA: {
244
244
  readonly kind: "root_or_external";
245
245
  };
246
246
  readonly startAttributes: {
247
+ readonly "pi.operation.kind": {
248
+ readonly type: "string";
249
+ readonly required: true;
250
+ readonly values: readonly ["navigation"];
251
+ readonly description: "Navigation operation kind";
252
+ };
247
253
  readonly "pi.session.id": {
248
254
  readonly type: "string";
249
255
  readonly required: true;
@@ -267,12 +273,6 @@ export declare const HARNESS_TELEMETRY_SCHEMA: {
267
273
  readonly required: true;
268
274
  readonly description: "Whether this invocation resumes durable work";
269
275
  };
270
- readonly "pi.operation.kind": {
271
- readonly type: "string";
272
- readonly required: true;
273
- readonly values: readonly ["navigation"];
274
- readonly description: "Navigation operation kind";
275
- };
276
276
  };
277
277
  readonly endAttributes: {
278
278
  readonly "pi.error.code": {
@@ -738,6 +738,12 @@ export declare const AGENT_TELEMETRY_SCHEMAS: readonly [{
738
738
  readonly kind: "root_or_external";
739
739
  };
740
740
  readonly startAttributes: {
741
+ readonly "pi.operation.kind": {
742
+ readonly type: "string";
743
+ readonly required: true;
744
+ readonly values: readonly ["run"];
745
+ readonly description: "Run operation kind";
746
+ };
741
747
  readonly "pi.session.id": {
742
748
  readonly type: "string";
743
749
  readonly required: true;
@@ -761,12 +767,6 @@ export declare const AGENT_TELEMETRY_SCHEMAS: readonly [{
761
767
  readonly required: true;
762
768
  readonly description: "Whether this invocation resumes durable work";
763
769
  };
764
- readonly "pi.operation.kind": {
765
- readonly type: "string";
766
- readonly required: true;
767
- readonly values: readonly ["run"];
768
- readonly description: "Run operation kind";
769
- };
770
770
  };
771
771
  readonly endAttributes: {
772
772
  readonly "pi.error.code": {
@@ -796,6 +796,12 @@ export declare const AGENT_TELEMETRY_SCHEMAS: readonly [{
796
796
  readonly kind: "root_or_external";
797
797
  };
798
798
  readonly startAttributes: {
799
+ readonly "pi.operation.kind": {
800
+ readonly type: "string";
801
+ readonly required: true;
802
+ readonly values: readonly ["compaction"];
803
+ readonly description: "Compaction operation kind";
804
+ };
799
805
  readonly "pi.session.id": {
800
806
  readonly type: "string";
801
807
  readonly required: true;
@@ -819,12 +825,6 @@ export declare const AGENT_TELEMETRY_SCHEMAS: readonly [{
819
825
  readonly required: true;
820
826
  readonly description: "Whether this invocation resumes durable work";
821
827
  };
822
- readonly "pi.operation.kind": {
823
- readonly type: "string";
824
- readonly required: true;
825
- readonly values: readonly ["compaction"];
826
- readonly description: "Compaction operation kind";
827
- };
828
828
  };
829
829
  readonly endAttributes: {
830
830
  readonly "pi.error.code": {
@@ -854,6 +854,12 @@ export declare const AGENT_TELEMETRY_SCHEMAS: readonly [{
854
854
  readonly kind: "root_or_external";
855
855
  };
856
856
  readonly startAttributes: {
857
+ readonly "pi.operation.kind": {
858
+ readonly type: "string";
859
+ readonly required: true;
860
+ readonly values: readonly ["navigation"];
861
+ readonly description: "Navigation operation kind";
862
+ };
857
863
  readonly "pi.session.id": {
858
864
  readonly type: "string";
859
865
  readonly required: true;
@@ -877,12 +883,6 @@ export declare const AGENT_TELEMETRY_SCHEMAS: readonly [{
877
883
  readonly required: true;
878
884
  readonly description: "Whether this invocation resumes durable work";
879
885
  };
880
- readonly "pi.operation.kind": {
881
- readonly type: "string";
882
- readonly required: true;
883
- readonly values: readonly ["navigation"];
884
- readonly description: "Navigation operation kind";
885
- };
886
886
  };
887
887
  readonly endAttributes: {
888
888
  readonly "pi.error.code": {
@@ -4,7 +4,7 @@ export function detectSupportedImageMimeType(buffer) {
4
4
  return buffer[3] === 0xf7 ? undefined : "image/jpeg";
5
5
  if (startsWith(buffer, PNG_SIGNATURE))
6
6
  return isPng(buffer) && !isAnimatedPng(buffer) ? "image/png" : undefined;
7
- if (startsWithAscii(buffer, 0, "GIF"))
7
+ if (startsWithAscii(buffer, 0, "GIF87a") || startsWithAscii(buffer, 0, "GIF89a"))
8
8
  return "image/gif";
9
9
  if (startsWithAscii(buffer, 0, "RIFF") && startsWithAscii(buffer, 8, "WEBP"))
10
10
  return "image/webp";
package/dist/proxy.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * Proxy stream function for apps that route LLM calls through a server.
3
3
  * The server manages auth and proxies requests to LLM providers.
4
4
  */
5
- import { type AssistantMessage, type AssistantMessageEvent, type Context, EventStream, type Model, type SimpleStreamOptions, type StopReason, type ToolCall } from "@earendil-works/pi-ai";
5
+ import { type AssistantMessage, type AssistantMessageEvent, EventStream, type Model, type SimpleStreamOptions, type StopReason, type ToolCall, type TranscriptContext } from "@earendil-works/pi-ai";
6
6
  declare class ProxyMessageEventStream extends EventStream<AssistantMessageEvent, AssistantMessage> {
7
7
  constructor();
8
8
  }
@@ -71,6 +71,6 @@ export interface ProxyStreamOptions extends ProxySerializableStreamOptions {
71
71
  /** Proxy server URL (e.g., "https://genai.example.com") */
72
72
  proxyUrl: string;
73
73
  }
74
- export declare function streamProxy(model: Model<any>, context: Context, options: ProxyStreamOptions): ProxyMessageEventStream;
74
+ export declare function streamProxy(model: Model<any>, context: TranscriptContext, options: ProxyStreamOptions): ProxyMessageEventStream;
75
75
  export {};
76
76
  //# sourceMappingURL=proxy.d.ts.map
package/dist/types.d.ts CHANGED
@@ -1,16 +1,20 @@
1
- import type { Api, AssistantMessage, AssistantMessageEvent, AssistantMessageEventStream, Context, CursorExecHandlers, ImageContent, Message, Model, ProviderDiagnostic, SimpleStreamOptions, TextContent, ThinkingSelection, Tool, ToolResultMessage, Usage } from "@earendil-works/pi-ai";
1
+ import type { Api, AssistantMessage, AssistantMessageEvent, AssistantMessageEventStream, CursorExecHandlers, ImageContent, JsonValue, Message, Model, ProviderDiagnostic, SimpleStreamOptions, TextContent, ThinkingSelection, Tool, ToolResultMessage, TranscriptContext, Usage } from "@earendil-works/pi-ai";
2
2
  import type { Static, TSchema } from "typebox";
3
3
  /**
4
4
  * Stream function used by the agent loop. `Models.streamSimple` satisfies
5
5
  * this shape.
6
6
  *
7
+ * The loop passes a normalized transcript: the system prompt and tool
8
+ * declarations are carried by the transcript's system messages, never by
9
+ * `context.systemPrompt` or `context.tools`.
10
+ *
7
11
  * Contract:
8
12
  * - Must not throw or return a rejected promise for request/model/runtime failures.
9
13
  * - Must return an AssistantMessageEventStream.
10
14
  * - Failures must be encoded in the returned stream via protocol events and a
11
15
  * final AssistantMessage with stopReason "error" or "aborted" and errorMessage.
12
16
  */
13
- export type StreamFn = (model: Model<Api>, context: Context, options?: SimpleStreamOptions) => AssistantMessageEventStream | Promise<AssistantMessageEventStream>;
17
+ export type StreamFn = (model: Model<Api>, context: TranscriptContext, options?: SimpleStreamOptions) => AssistantMessageEventStream | Promise<AssistantMessageEventStream>;
14
18
  /**
15
19
  * Configuration for how tool calls from a single assistant message are executed.
16
20
  *
@@ -57,13 +61,17 @@ export interface BeforeToolCallResult {
57
61
  * - `isError`: if provided, replaces the tool result error flag
58
62
  * - `usage`: if provided, replaces the tool result usage
59
63
  * - `terminate`: if provided, replaces the early-termination hint
64
+ * - `structuredContent`: if provided, replaces the structured content. If `content` is provided
65
+ * without it, the structured content is dropped, because it may no longer match the content.
66
+ * Return it along with `content` to keep it.
60
67
  *
61
- * Omitted fields keep the original executed tool result values.
68
+ * Other omitted fields keep the original executed tool result values.
62
69
  * There is no deep merge for `content`, `details`, or `usage`.
63
70
  */
64
71
  export interface AfterToolCallResult {
65
72
  content?: (TextContent | ImageContent)[];
66
73
  details?: unknown;
74
+ structuredContent?: JsonValue;
67
75
  isError?: boolean;
68
76
  /** Usage from the final tool execution itself, if available. Not used for main LLM context accounting. */
69
77
  usage?: Usage;
@@ -99,21 +107,36 @@ export interface AfterToolCallContext {
99
107
  /** Current agent context at the time the tool call is finalized. */
100
108
  context: AgentContext;
101
109
  }
102
- /** Context passed to `shouldStopAfterTurn`. */
103
- export interface ShouldStopAfterTurnContext {
110
+ /** Context passed to completed-turn callbacks. */
111
+ export interface AgentTurnContext {
104
112
  /** The assistant message that completed the turn. */
105
113
  message: AssistantMessage;
106
- /** Tool result messages passed to the preceding `turn_end` event. */
114
+ /** Tool result messages emitted for the completed turn. */
107
115
  toolResults: ToolResultMessage[];
108
116
  /** Current agent context after the turn's assistant message and tool results have been appended. */
109
117
  context: AgentContext;
110
118
  /** Messages that this loop invocation will return if it exits at this point. Prompt runs include the initial prompt messages; continuation runs do not include pre-existing context messages. */
111
119
  newMessages: AgentMessage[];
112
120
  }
121
+ /** Decision returned by {@link FinishTurn}. Returning undefined preserves normal scheduling. */
122
+ export type AgentTurnDecision = {
123
+ action: "continue";
124
+ } | {
125
+ action: "end";
126
+ };
127
+ /**
128
+ * Called after a completed assistant turn and all of its tool-result messages, but before `turn_end`.
129
+ * On a normal turn, `{ action: "continue" }` ensures one next provider request. Tool-result, steering, or
130
+ * follow-up scheduling can satisfy that request and adds no extra request; otherwise the loop continues once
131
+ * with the current context. Error and aborted responses remain hard exits.
132
+ */
133
+ export type FinishTurn = (turn: AgentTurnContext, signal?: AbortSignal) => AgentTurnDecision | void | Promise<AgentTurnDecision | undefined> | Promise<void>;
113
134
  /** Replacement runtime state used by the agent loop before starting another provider request. */
114
135
  export interface AgentLoopTurnUpdate {
115
136
  /** Context for the next provider request. */
116
137
  context?: AgentContext;
138
+ /** Messages to append before the next provider request, with normal lifecycle events. */
139
+ messages?: AgentMessage[];
117
140
  /** Model for the next provider request. */
118
141
  model?: Model<any>;
119
142
  /** Thinking level for the next provider request. */
@@ -123,7 +146,20 @@ export interface AgentLoopTurnUpdate {
123
146
  /** Whether the next provider request should abort a server-selected fallback. */
124
147
  abortServerSideFallback?: boolean;
125
148
  }
126
- export interface PrepareNextTurnContext extends ShouldStopAfterTurnContext {
149
+ /** Runtime state available immediately before a conversational provider request. */
150
+ export interface PrepareRequestContext {
151
+ context: AgentContext;
152
+ model: Model<any>;
153
+ thinkingLevel: ThinkingLevel;
154
+ }
155
+ /** Replacement runtime state for the provider request being prepared. */
156
+ export type AgentRequestUpdate = Omit<AgentLoopTurnUpdate, "messages">;
157
+ /**
158
+ * Called immediately before every conversational provider request, including the first.
159
+ * Pending messages have already been appended and emitted when this callback runs.
160
+ */
161
+ export type PrepareRequest = (request: PrepareRequestContext, signal?: AbortSignal) => AgentRequestUpdate | void | Promise<AgentRequestUpdate | undefined> | Promise<void>;
162
+ export interface PrepareNextTurnContext extends AgentTurnContext {
127
163
  }
128
164
  export interface AgentLoopConfig extends SimpleStreamOptions {
129
165
  model: Model<any>;
@@ -154,7 +190,7 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
154
190
  /**
155
191
  * Converts AgentMessage[] to LLM-compatible Message[] before each LLM call.
156
192
  *
157
- * Each AgentMessage must be converted to a UserMessage, AssistantMessage, or ToolResultMessage
193
+ * Each AgentMessage must be converted to a SystemMessage, UserMessage, AssistantMessage, or ToolResultMessage
158
194
  * that the LLM can understand. AgentMessages that cannot be converted (e.g., UI-only notifications,
159
195
  * status messages) should be filtered out.
160
196
  *
@@ -209,30 +245,33 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
209
245
  */
210
246
  getApiKey?: (provider: string) => Promise<string | undefined> | string | undefined;
211
247
  /**
212
- * Called after each turn fully completes and `turn_end` has been emitted.
213
- *
214
- * If it returns true, the loop emits `agent_end` and exits before polling steering or follow-up queues,
215
- * without starting another LLM call. The current assistant response and any tool executions finish normally.
216
- * This callback sees the completed-turn context and runs before `prepareNextTurn`.
217
- *
218
- * Use this to request a graceful stop after the current turn, e.g. before context gets too full.
219
- *
220
- * Contract: must not throw or reject. Throwing interrupts the low-level agent loop without producing a normal event sequence.
248
+ * Called after the assistant message and all tool-result messages have been emitted, immediately before `turn_end`.
249
+ * `{ action: "end" }` ends the run without polling queues or preparing another request.
250
+ * On a normal turn, `{ action: "continue" }` ensures one next provider request. Tool-result, steering, or
251
+ * follow-up scheduling can satisfy that request and adds no extra request; otherwise the loop continues once
252
+ * with the current context. Returning undefined preserves normal scheduling. Error and aborted responses remain
253
+ * hard exits.
221
254
  */
222
- shouldStopAfterTurn?: (context: ShouldStopAfterTurnContext) => boolean | Promise<boolean>;
255
+ finishTurn?: FinishTurn;
256
+ /**
257
+ * Called immediately before every conversational provider request, including the first.
258
+ * Pending messages have already been appended. The returned context, model, and thinking level
259
+ * replace the runtime values for this and later requests in the run. This hook does not poll queues.
260
+ */
261
+ prepareRequest?: PrepareRequest;
223
262
  /**
224
263
  * Called after each completed assistant turn, including a normal stop response, before the loop decides whether another provider request starts.
225
264
  * A terminating-tool continuation emits its `turn_start` boundary first so queue owners can clear
226
265
  * or replace pending input before preparation finishes and the continuation is admitted.
227
- * `shouldStopAfterTurn` and an empty terminating tool batch can end the run before preparation.
228
- * Return replacement context/model/thinking state to affect the next turn.
266
+ * `finishTurn` returning `{ action: "end" }` and an empty terminating tool batch can end the run before preparation.
267
+ * Return replacement context/model/thinking state or messages to append to affect the next turn.
229
268
  * Return undefined to keep using the current context/config.
230
269
  */
231
270
  prepareNextTurn?: (context: PrepareNextTurnContext) => AgentLoopTurnUpdate | undefined | Promise<AgentLoopTurnUpdate | undefined>;
232
271
  /**
233
272
  * Returns steering messages to inject into the conversation mid-run.
234
273
  *
235
- * Called after the current assistant turn finishes executing its tool calls, unless `shouldStopAfterTurn` exits first.
274
+ * Called after the current assistant turn finishes executing its tool calls, unless `finishTurn` ends the run.
236
275
  * If messages are returned, they are added to the context before the next LLM call.
237
276
  * Tool calls from the current assistant message are not skipped.
238
277
  *
@@ -339,7 +378,11 @@ export type AgentMessage = Message | CustomAgentMessages[keyof CustomAgentMessag
339
378
  * assigned arrays before storing them.
340
379
  */
341
380
  export interface AgentState {
342
- /** System prompt sent with each model request. */
381
+ /**
382
+ * Base system prompt sent with each model request (fork: assignable; the loop folds it into the
383
+ * leading system message through `normalizeContext()`). Later transcript system messages with
384
+ * `content` or `sections` still apply on top of it.
385
+ */
343
386
  systemPrompt: string;
344
387
  /** Active model used for future turns. */
345
388
  model: Model<any>;
@@ -352,12 +395,21 @@ export interface AgentState {
352
395
  thinkingSelection?: ThinkingSelection;
353
396
  /** First reasoning effort on the branch, used to preserve Responses cache prefixes. */
354
397
  reasoningBaseline?: string;
355
- /** Available tools. Assigning a new array copies the top-level array. */
398
+ /**
399
+ * Executable tools. Assigning a new array copies the top-level array.
400
+ *
401
+ * Differences from the tools declared in the transcript are announced to the model
402
+ * with a system message before the next request.
403
+ */
356
404
  set tools(tools: AgentTool<any>[]);
357
405
  get tools(): AgentTool<any>[];
358
406
  /** Tool list the provider receives when it differs from `tools`; see {@link AgentContext.declaredTools}. */
359
407
  declaredTools?: AgentTool<any>[];
360
- /** Conversation transcript. Assigning a new array copies the top-level array. */
408
+ /**
409
+ * Conversation transcript. Assigning a new array copies the top-level array.
410
+ *
411
+ * System messages in the transcript carry the prompt and tool declarations.
412
+ */
361
413
  set messages(messages: AgentMessage[]);
362
414
  get messages(): AgentMessage[];
363
415
  /**
@@ -376,25 +428,39 @@ export interface AgentState {
376
428
  readonly providerDiagnostic?: ProviderDiagnostic;
377
429
  }
378
430
  /** Final or partial result produced by a tool. */
379
- export interface AgentToolResult<T> {
431
+ export interface AgentToolResult<T = JsonValue | undefined> {
380
432
  /** Text or image content returned to the model. */
381
433
  content: (TextContent | ImageContent)[];
382
434
  /** Arbitrary structured details for logs or UI rendering. */
383
435
  details: T;
436
+ /**
437
+ * Machine-readable result matching the tool's `outputSchema`, for programmatic callers. Not sent
438
+ * to the model; `content` remains the model-facing result.
439
+ */
440
+ structuredContent?: JsonValue;
384
441
  /** Usage from the final tool execution itself, if available. Not used for main LLM context accounting. */
385
442
  usage?: Usage;
386
- /** Names of tools introduced by this result and available from this transcript point onward. */
387
- addedToolNames?: string[];
443
+ /**
444
+ * Report a failure without throwing. The model sees `content` as an error result, like a thrown
445
+ * error, but `details` and `structuredContent` are kept for the UI and programmatic callers.
446
+ */
447
+ isError?: boolean;
388
448
  /**
389
449
  * Hint that the agent should stop after the current tool batch.
390
450
  * Early termination only happens when every finalized tool result in the batch sets this to true.
391
451
  */
392
452
  terminate?: boolean;
393
453
  /**
394
- * Report a failure without throwing: `true` marks this result as a tool error while keeping
395
- * `content` and `details` intact for the model and renderers. Omitted or `false` means success.
454
+ * Names of tools introduced by this result and available from this transcript point onward
455
+ * (fork lazy-tool activation and tool-search native deferred loading).
396
456
  */
397
- isError?: boolean;
457
+ addedToolNames?: string[];
458
+ }
459
+ /** Final outcome of a tool call after hooks ran. */
460
+ export interface AgentToolCallOutcome {
461
+ toolCall: AgentToolCall;
462
+ result: AgentToolResult<any>;
463
+ isError: boolean;
398
464
  }
399
465
  /**
400
466
  * Callback used by tools to stream partial execution updates.
@@ -412,7 +478,15 @@ export interface AgentTool<TParameters extends TSchema = TSchema, TDetails = any
412
478
  * Must return an object that matches `TParameters`.
413
479
  */
414
480
  prepareArguments?: (args: unknown) => Static<TParameters>;
415
- /** Execute the tool call. Throw on failure instead of encoding errors in `content`. */
481
+ /**
482
+ * JSON Schema of `structuredContent` in successful results. Tools that declare it should always
483
+ * set `structuredContent`.
484
+ */
485
+ outputSchema?: TSchema;
486
+ /**
487
+ * Execute the tool call. Throw on failure, or return a result with `isError: true`; do not only
488
+ * describe the failure in `content`.
489
+ */
416
490
  execute: (toolCallId: string, params: Static<TParameters>, signal?: AbortSignal, onUpdate?: AgentToolUpdateCallback<TDetails>) => Promise<AgentToolResult<TDetails>>;
417
491
  /** Recovery policy for an effect whose durable intent exists but whose outcome is unknown. */
418
492
  replay?: "never" | "safe";
@@ -427,11 +501,14 @@ export interface AgentTool<TParameters extends TSchema = TSchema, TDetails = any
427
501
  }
428
502
  /** Context snapshot passed into the low-level agent loop. */
429
503
  export interface AgentContext {
430
- /** System prompt included with the request. */
431
- systemPrompt: string;
504
+ /**
505
+ * Fork shorthand for the leading system message; `buildProviderContext` folds it with
506
+ * `normalizeContext()`. Upstream-built contexts carry the prompt in transcript system messages.
507
+ */
508
+ systemPrompt?: string;
432
509
  /** Transcript visible to the model. */
433
510
  messages: AgentMessage[];
434
- /** Tools available for this run. */
511
+ /** Tools available for execution in this run. */
435
512
  tools?: AgentTool<any>[];
436
513
  /**
437
514
  * Superset of `tools` to declare to the provider, keeping the tool prefix byte-stable while the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/senpi-agent-core",
3
- "version": "2026.9.30",
3
+ "version": "2026.10.1-2",
4
4
  "description": "General-purpose agent with transport abstraction, state management, and attachment support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -18,6 +18,10 @@
18
18
  "types": "./dist/harness/context.d.ts",
19
19
  "import": "./dist/harness/context.js"
20
20
  },
21
+ "./experimental/pico3": {
22
+ "types": "./dist/harness/pico3/index.d.ts",
23
+ "import": "./dist/harness/pico3/index.js"
24
+ },
21
25
  "./harness/env/nodejs": {
22
26
  "types": "./dist/harness/env/nodejs.d.ts",
23
27
  "import": "./dist/harness/env/nodejs.js"
@@ -46,7 +50,7 @@
46
50
  "clean": "shx rm -rf dist",
47
51
  "generate-telemetry-docs": "node scripts/generate-telemetry-docs.ts",
48
52
  "check:telemetry-docs": "node scripts/generate-telemetry-docs.ts --check",
49
- "build": "tsgo -p tsconfig.build.json",
53
+ "build": "tsc -p tsconfig.build.json",
50
54
  "bench:session:timing": "vitest bench --config vitest.benchmark.config.ts",
51
55
  "bench:session:memory": "tsx --tsconfig benchmark/tsconfig.json benchmark/session/loaded-footprint.ts",
52
56
  "bench:session:allocations": "tsx --tsconfig benchmark/tsconfig.json benchmark/session/allocation-profile.ts",
@@ -57,9 +61,9 @@
57
61
  "prepublishOnly": "npm run build"
58
62
  },
59
63
  "dependencies": {
60
- "@earendil-works/chord": "0.85.1",
61
- "@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.9.30",
62
- "@earendil-works/pi-telemetry": "npm:@code-yeongyu/senpi-telemetry@2026.9.30",
64
+ "@earendil-works/chord": "0.99.1",
65
+ "@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.10.1-2",
66
+ "@earendil-works/pi-telemetry": "npm:@code-yeongyu/senpi-telemetry@2026.10.1-2",
63
67
  "diff": "9.0.0",
64
68
  "ignore": "7.0.9",
65
69
  "typebox": "1.3.34",