@rowan-agent/agent 0.9.11 → 0.9.13

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 (3) hide show
  1. package/dist/index.d.ts +165 -153
  2. package/dist/index.js +138 -28
  3. package/package.json +1 -1
package/dist/index.d.ts CHANGED
@@ -55,6 +55,159 @@ type ToolResult = {
55
55
  error?: string;
56
56
  };
57
57
 
58
+ interface BeforePhaseEvent {
59
+ type: "before_phase";
60
+ phaseId: string;
61
+ input: PhaseContext;
62
+ }
63
+ interface AfterPhaseEvent {
64
+ type: "after_phase";
65
+ phaseId: string;
66
+ output: PhaseOutput;
67
+ }
68
+ interface BeforePromptEvent {
69
+ type: "before_prompt";
70
+ phaseId: string;
71
+ input: PhaseContext;
72
+ }
73
+ interface BeforeToolCallEvent {
74
+ type: "before_tool_call";
75
+ tool: Tool$1;
76
+ args: unknown;
77
+ }
78
+ interface AfterToolCallEvent {
79
+ type: "after_tool_call";
80
+ tool: Tool$1;
81
+ result: ToolResult;
82
+ }
83
+ type HookEvent = BeforePhaseEvent | AfterPhaseEvent | BeforePromptEvent | BeforeToolCallEvent | AfterToolCallEvent;
84
+ interface BeforePhaseResult {
85
+ abort?: Outcome$1;
86
+ skip?: {
87
+ route: string;
88
+ message: string;
89
+ };
90
+ input?: PhaseContext;
91
+ }
92
+ interface AfterPhaseResult {
93
+ abort?: Outcome$1;
94
+ retry?: PhaseContext;
95
+ output?: PhaseOutput;
96
+ }
97
+ interface BeforePromptResult {
98
+ input?: PhaseContext;
99
+ }
100
+ interface BeforeToolCallResult {
101
+ allow: boolean;
102
+ reason?: string;
103
+ }
104
+ interface AfterToolCallResult {
105
+ result?: ToolResult;
106
+ }
107
+ interface HookResultMap {
108
+ before_phase: BeforePhaseResult | undefined;
109
+ after_phase: AfterPhaseResult | undefined;
110
+ before_prompt: BeforePromptResult | undefined;
111
+ before_tool_call: BeforeToolCallResult | undefined;
112
+ after_tool_call: AfterToolCallResult | undefined;
113
+ }
114
+ type HookEventType = HookEvent["type"];
115
+ type HookHandler<K extends HookEventType> = (event: Extract<HookEvent, {
116
+ type: K;
117
+ }>) => HookResultMap[K] | Promise<HookResultMap[K]> | void | Promise<void>;
118
+ declare class HooksManager {
119
+ private handlers;
120
+ on<K extends HookEventType>(eventType: K, handler: HookHandler<K>): void;
121
+ off<K extends HookEventType>(eventType: K, handler: HookHandler<K>): void;
122
+ emit<K extends HookEventType>(eventType: K, event: Extract<HookEvent, {
123
+ type: K;
124
+ }>): Promise<void>;
125
+ emitFirst<K extends HookEventType>(eventType: K, event: Extract<HookEvent, {
126
+ type: K;
127
+ }>): Promise<HookResultMap[K] | undefined>;
128
+ }
129
+
130
+ type LoopMetrics = {
131
+ /** Number of phase iterations executed. */
132
+ iterations: number;
133
+ /** Phase transition history. */
134
+ phaseTransitions: Array<{
135
+ from: string;
136
+ to: string;
137
+ ts: string;
138
+ }>;
139
+ /** Number of times compaction was triggered. */
140
+ compactionCount: number;
141
+ /** Number of retry attempts due to transient errors. */
142
+ retryCount: number;
143
+ /** Loop start timestamp. */
144
+ startedAt: string;
145
+ /** Loop start time as epoch ms (for duration calculation). */
146
+ startedAtMs: number;
147
+ /** Loop end timestamp (set on completion). */
148
+ endedAt?: string;
149
+ /** Total wall-clock duration in ms. */
150
+ durationMs?: number;
151
+ };
152
+ type ExecutionState = {
153
+ currentPhase: string;
154
+ attempt: number;
155
+ metrics: LoopMetrics;
156
+ status: "idle" | "running" | "suspended" | "completed" | "aborted" | "failed";
157
+ continuation?: ExecutionContinuationState;
158
+ phaseInteractions?: PhaseInteractionState;
159
+ };
160
+ type ExecutionContinuationState = {
161
+ isContinuing: boolean;
162
+ previousPayload?: unknown;
163
+ previousResults: Array<{
164
+ name: string;
165
+ output?: unknown;
166
+ }>;
167
+ pendingInstruction?: string;
168
+ previousPhaseMessageId?: string;
169
+ };
170
+
171
+ type PhaseInteractionKind = "user_input" | "permission" | "elicitation" | "confirmation";
172
+ type PhaseInteractionStatus = "pending" | "answered" | "denied" | "cancelled" | "expired";
173
+ type PhaseInteraction = Readonly<{
174
+ id: string;
175
+ phase: string;
176
+ kind: PhaseInteractionKind;
177
+ prompt: string;
178
+ payload?: JsonValue;
179
+ createdAt: string;
180
+ status: PhaseInteractionStatus;
181
+ }>;
182
+ type PhaseInteractionState = Readonly<{
183
+ requests: readonly PhaseInteraction[];
184
+ answers: Readonly<Record<string, JsonValue>>;
185
+ checkpoint?: JsonValue;
186
+ }>;
187
+ type PhaseInteractionDriver = Readonly<{
188
+ signal: AbortSignal;
189
+ request(input: Readonly<{
190
+ id?: string;
191
+ kind: PhaseInteractionKind;
192
+ prompt: string;
193
+ payload?: JsonValue;
194
+ }>): PhaseInteraction;
195
+ pending(): readonly PhaseInteraction[];
196
+ answers(): ReadonlyMap<string, JsonValue>;
197
+ suspend(input?: Readonly<{
198
+ checkpoint?: JsonValue;
199
+ }>): never;
200
+ }>;
201
+ declare class PhaseInteractionCancelledError extends Error {
202
+ readonly code: "phase_interaction_cancelled";
203
+ constructor();
204
+ }
205
+ declare class PhaseInteractionBoundary extends Error {
206
+ readonly state: ExecutionState;
207
+ readonly interactions: readonly PhaseInteraction[];
208
+ constructor(state: ExecutionState, interactions: readonly PhaseInteraction[]);
209
+ }
210
+
58
211
  /** Durable and transient DTOs owned by the event-driven Agent Runtime. */
59
212
 
60
213
  declare const opaqueIdBrand: unique symbol;
@@ -387,159 +540,6 @@ type PhaseStatusEvent = Readonly<{
387
540
  }>;
388
541
  type RunEvent = DurableRunEvent | MessageDelta | ThinkingDelta | ToolProgress | PhaseStatusEvent;
389
542
 
390
- interface BeforePhaseEvent {
391
- type: "before_phase";
392
- phaseId: string;
393
- input: PhaseContext;
394
- }
395
- interface AfterPhaseEvent {
396
- type: "after_phase";
397
- phaseId: string;
398
- output: PhaseOutput;
399
- }
400
- interface BeforePromptEvent {
401
- type: "before_prompt";
402
- phaseId: string;
403
- input: PhaseContext;
404
- }
405
- interface BeforeToolCallEvent {
406
- type: "before_tool_call";
407
- tool: Tool$1;
408
- args: unknown;
409
- }
410
- interface AfterToolCallEvent {
411
- type: "after_tool_call";
412
- tool: Tool$1;
413
- result: ToolResult;
414
- }
415
- type HookEvent = BeforePhaseEvent | AfterPhaseEvent | BeforePromptEvent | BeforeToolCallEvent | AfterToolCallEvent;
416
- interface BeforePhaseResult {
417
- abort?: Outcome$1;
418
- skip?: {
419
- route: string;
420
- message: string;
421
- };
422
- input?: PhaseContext;
423
- }
424
- interface AfterPhaseResult {
425
- abort?: Outcome$1;
426
- retry?: PhaseContext;
427
- output?: PhaseOutput;
428
- }
429
- interface BeforePromptResult {
430
- input?: PhaseContext;
431
- }
432
- interface BeforeToolCallResult {
433
- allow: boolean;
434
- reason?: string;
435
- }
436
- interface AfterToolCallResult {
437
- result?: ToolResult;
438
- }
439
- interface HookResultMap {
440
- before_phase: BeforePhaseResult | undefined;
441
- after_phase: AfterPhaseResult | undefined;
442
- before_prompt: BeforePromptResult | undefined;
443
- before_tool_call: BeforeToolCallResult | undefined;
444
- after_tool_call: AfterToolCallResult | undefined;
445
- }
446
- type HookEventType = HookEvent["type"];
447
- type HookHandler<K extends HookEventType> = (event: Extract<HookEvent, {
448
- type: K;
449
- }>) => HookResultMap[K] | Promise<HookResultMap[K]> | void | Promise<void>;
450
- declare class HooksManager {
451
- private handlers;
452
- on<K extends HookEventType>(eventType: K, handler: HookHandler<K>): void;
453
- off<K extends HookEventType>(eventType: K, handler: HookHandler<K>): void;
454
- emit<K extends HookEventType>(eventType: K, event: Extract<HookEvent, {
455
- type: K;
456
- }>): Promise<void>;
457
- emitFirst<K extends HookEventType>(eventType: K, event: Extract<HookEvent, {
458
- type: K;
459
- }>): Promise<HookResultMap[K] | undefined>;
460
- }
461
-
462
- type LoopMetrics = {
463
- /** Number of phase iterations executed. */
464
- iterations: number;
465
- /** Phase transition history. */
466
- phaseTransitions: Array<{
467
- from: string;
468
- to: string;
469
- ts: string;
470
- }>;
471
- /** Number of times compaction was triggered. */
472
- compactionCount: number;
473
- /** Number of retry attempts due to transient errors. */
474
- retryCount: number;
475
- /** Loop start timestamp. */
476
- startedAt: string;
477
- /** Loop start time as epoch ms (for duration calculation). */
478
- startedAtMs: number;
479
- /** Loop end timestamp (set on completion). */
480
- endedAt?: string;
481
- /** Total wall-clock duration in ms. */
482
- durationMs?: number;
483
- };
484
- type ExecutionState = {
485
- currentPhase: string;
486
- attempt: number;
487
- metrics: LoopMetrics;
488
- status: "idle" | "running" | "suspended" | "completed" | "aborted" | "failed";
489
- continuation?: ExecutionContinuationState;
490
- phaseInteractions?: PhaseInteractionState;
491
- };
492
- type ExecutionContinuationState = {
493
- isContinuing: boolean;
494
- previousPayload?: unknown;
495
- previousResults: Array<{
496
- name: string;
497
- output?: unknown;
498
- }>;
499
- pendingInstruction?: string;
500
- previousPhaseMessageId?: string;
501
- };
502
-
503
- type PhaseInteractionKind = "user_input" | "permission" | "elicitation" | "confirmation";
504
- type PhaseInteractionStatus = "pending" | "answered" | "denied" | "cancelled" | "expired";
505
- type PhaseInteraction = Readonly<{
506
- id: string;
507
- phase: string;
508
- kind: PhaseInteractionKind;
509
- prompt: string;
510
- payload?: JsonValue;
511
- createdAt: string;
512
- status: PhaseInteractionStatus;
513
- }>;
514
- type PhaseInteractionState = Readonly<{
515
- requests: readonly PhaseInteraction[];
516
- answers: Readonly<Record<string, JsonValue>>;
517
- checkpoint?: JsonValue;
518
- }>;
519
- type PhaseInteractionDriver = Readonly<{
520
- signal: AbortSignal;
521
- request(input: Readonly<{
522
- id?: string;
523
- kind: PhaseInteractionKind;
524
- prompt: string;
525
- payload?: JsonValue;
526
- }>): PhaseInteraction;
527
- pending(): readonly PhaseInteraction[];
528
- answers(): ReadonlyMap<string, JsonValue>;
529
- suspend(input?: Readonly<{
530
- checkpoint?: JsonValue;
531
- }>): never;
532
- }>;
533
- declare class PhaseInteractionCancelledError extends Error {
534
- readonly code: "phase_interaction_cancelled";
535
- constructor();
536
- }
537
- declare class PhaseInteractionBoundary extends Error {
538
- readonly state: ExecutionState;
539
- readonly interactions: readonly PhaseInteraction[];
540
- constructor(state: ExecutionState, interactions: readonly PhaseInteraction[]);
541
- }
542
-
543
543
  type ModelInvokeOutput = {
544
544
  text: string;
545
545
  contentBlocks: ContentBlock[];
@@ -932,6 +932,8 @@ type PhaseExecutionIdentity = Readonly<{
932
932
  agentId: string;
933
933
  runId: string;
934
934
  executionId: string;
935
+ /** Original durable Run input, before any Phase-local interpretation. */
936
+ input?: UserContent;
935
937
  /** Opaque metadata captured at the durable boundary; Rowan does not decode it. */
936
938
  agentMetadata?: Readonly<Record<string, unknown>>;
937
939
  runMetadata?: Readonly<Record<string, unknown>>;
@@ -1378,6 +1380,8 @@ type AgentConfiguration = Readonly<{
1378
1380
  }>;
1379
1381
  resourceView: ResourceView;
1380
1382
  contexts?: readonly ContextCandidate[];
1383
+ /** Trusted Host-owned Contexts rendered as durable user messages. */
1384
+ additionalContexts?: readonly ContextCandidate[];
1381
1385
  cwd?: string;
1382
1386
  maxAttempts?: number;
1383
1387
  model: ModelConfig | ModelRef;
@@ -1394,6 +1398,7 @@ type ConfigurationSnapshot = Readonly<{
1394
1398
  refs: Readonly<Record<ResourceKind, readonly ResourceRef[]>>;
1395
1399
  }>;
1396
1400
  contexts: readonly ContextCandidate[];
1401
+ additionalContexts: readonly ContextCandidate[];
1397
1402
  resourceView: ResourceView;
1398
1403
  cwd?: string;
1399
1404
  maxAttempts?: number;
@@ -1502,6 +1507,8 @@ type AgentConfig = Readonly<{
1502
1507
  identity: string;
1503
1508
  definition: AgentDefinition;
1504
1509
  resources: AgentResources;
1510
+ /** Host-owned Contexts are rendered as durable user messages for a Run. */
1511
+ additionalContexts?: readonly ContextCandidate[];
1505
1512
  cwd?: string;
1506
1513
  maxAttempts?: number;
1507
1514
  beforeToolCall?: BeforeToolCall;
@@ -1766,6 +1773,7 @@ interface OwnedStore {
1766
1773
  executionId?: ExecutionId;
1767
1774
  messageId?: MessageId;
1768
1775
  configToken?: ConfigToken;
1776
+ inputContext?: UserInput;
1769
1777
  }): Promise<RunClaim>;
1770
1778
  failQueuedRun(input: {
1771
1779
  runId: RunId;
@@ -1938,6 +1946,7 @@ interface AgentRuntime$1 {
1938
1946
  contextWindow?: number;
1939
1947
  }): Promise<ContextStatus>;
1940
1948
  compactContext(agentId: AgentId, options?: {
1949
+ input?: UserInput;
1941
1950
  instructions?: string;
1942
1951
  idempotencyKey?: string;
1943
1952
  }): Promise<AgentRun>;
@@ -2029,6 +2038,7 @@ declare class AgentRuntime implements AgentRuntime$1 {
2029
2038
  contextWindow?: number;
2030
2039
  }): Promise<ContextStatus>;
2031
2040
  compactContext(agentId: AgentId, options?: {
2041
+ input?: UserInput;
2032
2042
  instructions?: string;
2033
2043
  idempotencyKey?: string;
2034
2044
  }): Promise<AgentRun>;
@@ -2178,6 +2188,7 @@ declare class InMemoryStore implements DurableStore {
2178
2188
  executionId?: ExecutionId;
2179
2189
  messageId?: MessageId;
2180
2190
  configToken?: ConfigToken;
2191
+ inputContext?: UserInput;
2181
2192
  }): RunClaim;
2182
2193
  failQueuedRun(lease: OwnerLease, input: {
2183
2194
  runId: RunId;
@@ -2361,6 +2372,7 @@ declare class SqliteStore implements DurableStore {
2361
2372
  executionId?: ExecutionId;
2362
2373
  messageId?: MessageId;
2363
2374
  configToken?: ConfigToken;
2375
+ inputContext?: UserInput;
2364
2376
  }): Promise<RunClaim>;
2365
2377
  failQueuedRun(lease: OwnerLease, input: {
2366
2378
  runId: RunId;
package/dist/index.js CHANGED
@@ -46,6 +46,9 @@ function buildContextDescription(contexts) {
46
46
  lines.push("</agent_context>");
47
47
  return lines.join("\n");
48
48
  }
49
+ function buildAdditionalContextMessage(contexts) {
50
+ return buildContextDescription(contexts);
51
+ }
49
52
  function formatResourceOutput(resource) {
50
53
  const parts = [`<${resource.type} name="${escapeXml(resource.name)}" location="${escapeXml(resource.location)}">`];
51
54
  if (resource.baseDir) {
@@ -1751,7 +1754,7 @@ async function loadPhaseCode(codePath) {
1751
1754
  throw new Error(`Phase code at "${codePath}" must export a default function or a run() function.`);
1752
1755
  }
1753
1756
 
1754
- // src/harness/phases/default.ts
1757
+ // src/harness/phases/core-phases.ts
1755
1758
  var DEFAULT_PHASE_ID = "default";
1756
1759
  var STOP_PHASE_ID = "stop";
1757
1760
  var COMPACT_PHASE_ID = "compact";
@@ -1809,6 +1812,14 @@ function createCompactPhase() {
1809
1812
  });
1810
1813
  const tools = context.tools.filter((tool) => tool.core && (tool.name === "read" || tool.name === "bash"));
1811
1814
  const working = { ...context, tools, skills: [...context.skills], messages: [...context.messages] };
1815
+ const instructions = compactInstructions(context);
1816
+ if (instructions) {
1817
+ working.messages.push(createMessage("user", `Additional compaction instructions:
1818
+ ${instructions}`, {
1819
+ kind: "phase_input",
1820
+ phase: COMPACT_PHASE_ID
1821
+ }));
1822
+ }
1812
1823
  let result = await execution.invokeModel(working, { output: "internal" });
1813
1824
  for (let attempt = 0; attempt < 8 && result.toolCalls.length > 0; attempt += 1) {
1814
1825
  const calls = result.toolCalls.filter((call) => tools.some((tool) => tool.name === call.name));
@@ -1860,7 +1871,11 @@ function createCompactPhase() {
1860
1871
  return {
1861
1872
  route: "stop",
1862
1873
  phase: COMPACT_PHASE_ID,
1863
- payload: { kind: "context_compaction", summary: result.text },
1874
+ payload: {
1875
+ kind: "context_compaction",
1876
+ summary: result.text,
1877
+ ...instructions ? { instructions } : {}
1878
+ },
1864
1879
  status: {
1865
1880
  state: "completed",
1866
1881
  kind: "compacted",
@@ -1870,6 +1885,21 @@ function createCompactPhase() {
1870
1885
  }
1871
1886
  };
1872
1887
  }
1888
+ function compactInstructions(context) {
1889
+ const rowan = context.execution.runMetadata?.rowan;
1890
+ if (typeof rowan === "object" && rowan !== null && !Array.isArray(rowan)) {
1891
+ const configured = rowan.instructions;
1892
+ if (typeof configured === "string" && configured.trim().length > 0) {
1893
+ return configured.trim();
1894
+ }
1895
+ }
1896
+ const input = context.execution.input;
1897
+ if (input === void 0) return void 0;
1898
+ const text = typeof input === "string" ? input : input.filter((part) => part.type === "text").map((part) => part.text).join("");
1899
+ const match = text.match(/^\/compact(?:\s+([\s\S]*))?$/i);
1900
+ const instructions = match?.[1]?.trim();
1901
+ return instructions || void 0;
1902
+ }
1873
1903
  function createCorePhases() {
1874
1904
  return [createDefaultPhase(), createStopPhase(), createCompactPhase()];
1875
1905
  }
@@ -2096,7 +2126,7 @@ function findLatestUserInputMessage(messages) {
2096
2126
  for (let index = messages.length - 1; index >= 0; index -= 1) {
2097
2127
  const message = messages[index];
2098
2128
  const kind = message.metadata?.kind;
2099
- if (message.role === "user" && kind !== "phase_prompt" && kind !== "phase_input") {
2129
+ if (message.role === "user" && kind !== "phase_prompt" && kind !== "phase_input" && kind !== "host_context") {
2100
2130
  return message;
2101
2131
  }
2102
2132
  }
@@ -3249,6 +3279,7 @@ function isContinuation(value) {
3249
3279
  }
3250
3280
 
3251
3281
  // src/runtime/contracts.ts
3282
+ var HOST_CONTEXT_MESSAGE_KIND = "host_context";
3252
3283
  var THINKING_LEVELS = [
3253
3284
  "off",
3254
3285
  "minimal",
@@ -3271,7 +3302,7 @@ function thinkingLevelFromMessages(messages) {
3271
3302
  if (message?.role !== "user") continue;
3272
3303
  const metadata = message.metadata;
3273
3304
  const kind = isRecord3(metadata) && typeof metadata.kind === "string" ? metadata.kind : void 0;
3274
- if (kind === "phase_prompt" || kind === "phase_input") continue;
3305
+ if (kind === "phase_prompt" || kind === "phase_input" || kind === HOST_CONTEXT_MESSAGE_KIND) continue;
3275
3306
  return thinkingLevelFromMetadata(metadata);
3276
3307
  }
3277
3308
  return void 0;
@@ -3325,6 +3356,9 @@ function normalizeUserInput(input) {
3325
3356
  assertJsonValue(normalized, "input");
3326
3357
  return normalized;
3327
3358
  }
3359
+ function canonicalUserInput(input) {
3360
+ return canonicalJson(normalizeUserInput(input));
3361
+ }
3328
3362
  function isAssistantMessage(value) {
3329
3363
  return isRecord3(value) && hasOnlyKeys(value, ["id", "agentId", "runId", "messageRevision", "role", "content", "metadata", "sequenceWithinRun", "createdAt", "interrupted"]) && typeof value.id === "string" && typeof value.agentId === "string" && typeof value.runId === "string" && value.role === "assistant" && (value.messageRevision === void 0 || Number.isInteger(value.messageRevision) && value.messageRevision >= 0) && Number.isInteger(value.sequenceWithinRun) && value.sequenceWithinRun >= 0 && typeof value.createdAt === "string" && isAssistantContent(value.content) && (value.metadata === void 0 || isMetadata(value.metadata)) && (value.interrupted === void 0 || typeof value.interrupted === "boolean");
3330
3364
  }
@@ -3351,6 +3385,12 @@ function assertAgentConfig(config) {
3351
3385
  names.add(context.name);
3352
3386
  assertJsonValue(context.value, `Context candidate "${context.name}" value`);
3353
3387
  }
3388
+ for (const context of config.additionalContexts ?? []) {
3389
+ if (typeof context.name !== "string" || context.name.trim() === "") {
3390
+ throw new TypeError("Additional Context candidate name must be non-empty");
3391
+ }
3392
+ assertJsonValue(context.value, `Additional Context candidate "${context.name}" value`);
3393
+ }
3354
3394
  }
3355
3395
  function assertAgentConfigRequest(config) {
3356
3396
  if (!isAgentConfiguration(config)) {
@@ -3378,6 +3418,10 @@ function assertAgentConfigRequest(config) {
3378
3418
  if (typeof context.name !== "string" || context.name.trim() === "") throw new TypeError("Context candidate name must be non-empty");
3379
3419
  assertJsonValue(context.value, `Context candidate "${context.name}" value`);
3380
3420
  }
3421
+ for (const context of config.additionalContexts ?? []) {
3422
+ if (typeof context.name !== "string" || context.name.trim() === "") throw new TypeError("Additional Context candidate name must be non-empty");
3423
+ assertJsonValue(context.value, `Additional Context candidate "${context.name}" value`);
3424
+ }
3381
3425
  }
3382
3426
  function assertToolExecutionResult(value) {
3383
3427
  if (!isToolResult(value)) throw new TypeError("Tool result must be JSON-safe and contain no Runtime identity");
@@ -3514,7 +3558,17 @@ function snapshotConfig(config) {
3514
3558
  })))
3515
3559
  } : {}
3516
3560
  });
3517
- return Object.freeze({ ...config, definition, resources });
3561
+ return Object.freeze({
3562
+ ...config,
3563
+ definition,
3564
+ resources,
3565
+ ...config.additionalContexts ? {
3566
+ additionalContexts: Object.freeze(config.additionalContexts.map((context) => Object.freeze({
3567
+ name: context.name,
3568
+ value: snapshotJsonValue(context.value)
3569
+ })))
3570
+ } : {}
3571
+ });
3518
3572
  }
3519
3573
  function snapshotPhaseRegistry(registry) {
3520
3574
  const phases = /* @__PURE__ */ new Map();
@@ -3563,6 +3617,12 @@ function snapshotConfiguration(config) {
3563
3617
  name: context.name,
3564
3618
  value: snapshotJsonValue(context.value)
3565
3619
  })))
3620
+ } : {},
3621
+ ...config.additionalContexts ? {
3622
+ additionalContexts: Object.freeze(config.additionalContexts.map((context) => Object.freeze({
3623
+ name: context.name,
3624
+ value: snapshotJsonValue(context.value)
3625
+ })))
3566
3626
  } : {}
3567
3627
  });
3568
3628
  }
@@ -5474,6 +5534,8 @@ function resolveConfigurationSnapshot(registry, input) {
5474
5534
  throw new Error(`Agent Definition "${input.definition.name}" is not available in the Resource View.`);
5475
5535
  }
5476
5536
  const layer = input.definition.layer;
5537
+ const selectedContexts = selectNamedResources(input.contexts ?? [], base.contexts, "Context");
5538
+ const additionalContexts = deduplicateContexts(input.additionalContexts ?? [], selectedContexts);
5477
5539
  const definition = applyDefinitionLayer(base, layer);
5478
5540
  const tools = selectNamedResources(resolved.tools, definition.tools, "Tool");
5479
5541
  const skills = mergeSkills(
@@ -5481,7 +5543,6 @@ function resolveConfigurationSnapshot(registry, input) {
5481
5543
  definition.bundledSkills
5482
5544
  );
5483
5545
  const phases = resolvePhases(resolved.phases, definition.phases);
5484
- const contexts = selectNamedResources(input.contexts ?? [], base.contexts, "Context");
5485
5546
  return {
5486
5547
  identity: input.identity,
5487
5548
  definition,
@@ -5497,7 +5558,8 @@ function resolveConfigurationSnapshot(registry, input) {
5497
5558
  phase: selectedRefs(resolved.refs.phase, [...phases?.phases.keys() ?? []])
5498
5559
  }
5499
5560
  },
5500
- contexts,
5561
+ contexts: selectedContexts,
5562
+ additionalContexts,
5501
5563
  resourceView: input.resourceView,
5502
5564
  ...input.cwd === void 0 ? {} : { cwd: input.cwd },
5503
5565
  ...input.maxAttempts === void 0 ? {} : { maxAttempts: input.maxAttempts },
@@ -5522,6 +5584,7 @@ function materializeConfigurationSnapshot(snapshot) {
5522
5584
  ],
5523
5585
  resourceRevisions: snapshot.resources.revisions
5524
5586
  },
5587
+ ...snapshot.additionalContexts.length > 0 ? { additionalContexts: snapshot.additionalContexts } : {},
5525
5588
  ...snapshot.cwd === void 0 ? {} : { cwd: snapshot.cwd },
5526
5589
  ...snapshot.maxAttempts === void 0 ? {} : { maxAttempts: snapshot.maxAttempts },
5527
5590
  ..."stream" in snapshot && snapshot.stream ? { model: snapshot.model, stream: snapshot.stream } : { model: snapshot.model }
@@ -5539,6 +5602,16 @@ function applyDefinitionLayer(base, layer) {
5539
5602
  ...layer.phases === void 0 ? {} : { phases: intersectPhaseSelection(base.phases, layer.phases) }
5540
5603
  };
5541
5604
  }
5605
+ function deduplicateContexts(additional, existing = []) {
5606
+ const contexts = [];
5607
+ const seen = new Set(existing.map(({ name }) => name));
5608
+ for (const context of additional) {
5609
+ if (seen.has(context.name)) continue;
5610
+ seen.add(context.name);
5611
+ contexts.push(context);
5612
+ }
5613
+ return contexts;
5614
+ }
5542
5615
  function intersectNames(parent, layer, kind) {
5543
5616
  if (parent === void 0) return [...layer];
5544
5617
  const parentNames = new Set(parent);
@@ -5735,7 +5808,9 @@ var AgentRuntime = class _AgentRuntime {
5735
5808
  };
5736
5809
  const run = await this.owned.createRun({
5737
5810
  agentId,
5738
- input: "",
5811
+ // An empty input is a system-triggered Control Run. A non-empty input
5812
+ // represents a user invocation and is committed by the Durable Store.
5813
+ input: options.input ?? "",
5739
5814
  metadata,
5740
5815
  idempotencyKey: options.idempotencyKey ?? createId("compact")
5741
5816
  });
@@ -5974,7 +6049,13 @@ var AgentRuntime = class _AgentRuntime {
5974
6049
  }
5975
6050
  }
5976
6051
  const executionId = createId("exec");
5977
- claim = await this.owned.claimRun({ runId: run.id, expectedRevision: run.revision, executionId, configToken: token });
6052
+ claim = await this.owned.claimRun({
6053
+ runId: run.id,
6054
+ expectedRevision: run.revision,
6055
+ executionId,
6056
+ configToken: token,
6057
+ ...controlKind ? {} : { inputContext: additionalContextInput(config) }
6058
+ });
5978
6059
  executionRevision = claim.run.revision;
5979
6060
  const controller = new AbortController();
5980
6061
  this.executions.set(run.id, { controller, executionId });
@@ -6002,17 +6083,6 @@ var AgentRuntime = class _AgentRuntime {
6002
6083
  ...executionContext2.phases,
6003
6084
  entryPhaseId: COMPACT_PHASE_ID
6004
6085
  };
6005
- const instructions = controlKind === "compact" ? compactInstructions(run) : void 0;
6006
- if (instructions) {
6007
- executionContext2.messages.push({
6008
- id: createId("msg"),
6009
- role: "user",
6010
- content: `Additional compaction instructions:
6011
- ${instructions}`,
6012
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
6013
- metadata: { kind: "phase_input", phase: COMPACT_PHASE_ID }
6014
- });
6015
- }
6016
6086
  }
6017
6087
  return executionContext2;
6018
6088
  };
@@ -6029,6 +6099,7 @@ ${instructions}`,
6029
6099
  agentId: run.agentId,
6030
6100
  runId: run.id,
6031
6101
  executionId: claim.execution.executionId,
6102
+ input: typeof run.input === "string" ? run.input : run.input.content,
6032
6103
  ...agent.metadata === void 0 ? {} : { agentMetadata: agent.metadata },
6033
6104
  ...run.metadata === void 0 ? {} : { runMetadata: run.metadata }
6034
6105
  },
@@ -6175,6 +6246,7 @@ ${instructions}`,
6175
6246
  const output = latestAssistant(run, result.messages.slice(modelMessages.length), modelMessages.length);
6176
6247
  if (controlKind === "compact" || isCompactionOutcome(result.outcome.payload)) {
6177
6248
  const summary = compactSummary(result.outcome.payload);
6249
+ const instructions = compactInstructions2(run) ?? compactOutputInstructions(result.outcome.payload);
6178
6250
  if (summary) {
6179
6251
  const covered = claim.history.at(-1);
6180
6252
  const record = {
@@ -6182,7 +6254,7 @@ ${instructions}`,
6182
6254
  agentId: run.agentId,
6183
6255
  summary,
6184
6256
  ...covered ? { coveredThrough: { messageId: covered.id, sequence: covered.sequenceWithinRun } } : {},
6185
- ...compactInstructions(run) ? { instructions: compactInstructions(run) } : {},
6257
+ ...instructions ? { instructions } : {},
6186
6258
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
6187
6259
  };
6188
6260
  await this.owned.commitContextCompaction(record);
@@ -6452,6 +6524,12 @@ ${instructions}`,
6452
6524
  this.heartbeat.unref?.();
6453
6525
  }
6454
6526
  };
6527
+ function additionalContextInput(config) {
6528
+ const contexts = config.additionalContexts ?? [];
6529
+ if (contexts.length === 0) return void 0;
6530
+ const content = buildAdditionalContextMessage(contexts);
6531
+ return content.length === 0 ? void 0 : { content, metadata: { kind: HOST_CONTEXT_MESSAGE_KIND } };
6532
+ }
6455
6533
  var DurableRun = class {
6456
6534
  constructor(runtime, id) {
6457
6535
  this.runtime = runtime;
@@ -6527,7 +6605,7 @@ function controlRunKind(run) {
6527
6605
  const kind = rowan.kind;
6528
6606
  return typeof kind === "string" ? kind : void 0;
6529
6607
  }
6530
- function compactInstructions(run) {
6608
+ function compactInstructions2(run) {
6531
6609
  const rowan = run.metadata?.rowan;
6532
6610
  if (typeof rowan !== "object" || rowan === null || !("instructions" in rowan)) return void 0;
6533
6611
  const instructions = rowan.instructions;
@@ -6538,6 +6616,11 @@ function compactSummary(payload) {
6538
6616
  const summary = payload.summary;
6539
6617
  return typeof summary === "string" && summary.trim().length > 0 ? summary : void 0;
6540
6618
  }
6619
+ function compactOutputInstructions(payload) {
6620
+ if (typeof payload !== "object" || payload === null || !("instructions" in payload)) return void 0;
6621
+ const instructions = payload.instructions;
6622
+ return typeof instructions === "string" && instructions.trim().length > 0 ? instructions.trim() : void 0;
6623
+ }
6541
6624
  function isCompactionOutcome(payload) {
6542
6625
  return typeof payload === "object" && payload !== null && "kind" in payload && payload.kind === "context_compaction";
6543
6626
  }
@@ -6995,7 +7078,14 @@ var InMemoryStore = class _InMemoryStore {
6995
7078
  this.assertOwner(lease);
6996
7079
  const executionId = input.executionId ?? createId("exec");
6997
7080
  const operationKey = `claim:${executionId}`;
6998
- const operationPayload = canonicalJson([input.runId, input.expectedRevision, input.messageId ?? null, input.configToken ?? null]);
7081
+ const inputContext = input.inputContext === void 0 ? void 0 : normalizeUserInput(input.inputContext);
7082
+ const operationPayload = canonicalJson([
7083
+ input.runId,
7084
+ input.expectedRevision,
7085
+ input.messageId ?? null,
7086
+ input.configToken ?? null,
7087
+ inputContext ?? null
7088
+ ]);
6999
7089
  const replay = this.replayOperation(operationKey, operationPayload);
7000
7090
  if (replay) return clone(replay);
7001
7091
  const run = this.requireRun(input.runId);
@@ -7011,10 +7101,25 @@ var InMemoryStore = class _InMemoryStore {
7011
7101
  throw new RuntimeError("run_state_conflict", { runId: run.id, expected: ["queued"], actual: run.state });
7012
7102
  }
7013
7103
  if (!run.pinnedConfigToken && input.configToken) run.pinnedConfigToken = input.configToken;
7014
- let message;
7015
- if (!run.checkpoint && !run.initialMessageId && !isControlRun(run)) {
7104
+ const committedMessages = [];
7105
+ const existingMessages = this.messagesForRun(run.id);
7106
+ if (inputContext && hasUserInput(inputContext) && !isControlRun(run) && !existingMessages.some((message) => message.role === "user" && message.metadata?.kind === HOST_CONTEXT_MESSAGE_KIND && canonicalUserInput({ content: message.content, metadata: message.metadata }) === canonicalUserInput(inputContext))) {
7107
+ const contextMessage = {
7108
+ id: createId("msg"),
7109
+ agentId: run.agentId,
7110
+ runId: run.id,
7111
+ role: "user",
7112
+ content: userInputContent(inputContext),
7113
+ ...userInputMetadata(inputContext) ? { metadata: clone(userInputMetadata(inputContext)) } : {},
7114
+ sequenceWithinRun: this.nextMessageSequence(run.id),
7115
+ createdAt: createTimestamp()
7116
+ };
7117
+ this.messages.set(contextMessage.id, contextMessage);
7118
+ committedMessages.push(contextMessage);
7119
+ }
7120
+ if (!run.checkpoint && !run.initialMessageId && (!isControlRun(run) || hasUserInput(run.input))) {
7016
7121
  const userInput = normalizeUserInput(run.input);
7017
- message = {
7122
+ const message = {
7018
7123
  id: input.messageId ?? createId("msg"),
7019
7124
  agentId: run.agentId,
7020
7125
  runId: run.id,
@@ -7025,6 +7130,7 @@ var InMemoryStore = class _InMemoryStore {
7025
7130
  createdAt: createTimestamp()
7026
7131
  };
7027
7132
  this.messages.set(message.id, message);
7133
+ committedMessages.push(message);
7028
7134
  }
7029
7135
  const execution = {
7030
7136
  runId: run.id,
@@ -7035,7 +7141,7 @@ var InMemoryStore = class _InMemoryStore {
7035
7141
  run.execution = execution;
7036
7142
  run.revision += 1;
7037
7143
  run.updatedAt = createTimestamp();
7038
- if (message) this.appendMessage(run, message);
7144
+ for (const message of committedMessages) this.appendMessage(run, message);
7039
7145
  this.appendTransition(run, "queued", "running");
7040
7146
  const result = { run: clone(run), execution: clone(execution), history: this.activeHistory(run.agentId, run.agentSequence) };
7041
7147
  this.writeOperationReceipt(operationKey, operationPayload, result);
@@ -7930,12 +8036,16 @@ function ownershipLost(expected, actual, reason) {
7930
8036
  function userInputContent(input) {
7931
8037
  return typeof input === "string" ? input : input.content;
7932
8038
  }
8039
+ function hasUserInput(input) {
8040
+ const content = userInputContent(input);
8041
+ return content.length > 0;
8042
+ }
7933
8043
  function userInputMetadata(input) {
7934
8044
  return typeof input === "string" ? void 0 : input.metadata;
7935
8045
  }
7936
8046
  function isControlRun(run) {
7937
8047
  const rowan = run.metadata?.rowan;
7938
- return typeof rowan === "object" && rowan !== null && "kind" in rowan && rowan.kind === "compact";
8048
+ return typeof rowan === "object" && rowan !== null && typeof rowan.kind === "string" && rowan.kind.length > 0;
7939
8049
  }
7940
8050
  function estimateMessageTokens(messages) {
7941
8051
  let characters = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rowan-agent/agent",
3
- "version": "0.9.11",
3
+ "version": "0.9.13",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",