@rowan-agent/agent 0.9.10 → 0.9.12

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 +159 -153
  2. package/dist/index.js +79 -20
  3. package/package.json +2 -2
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 appended after Definition selection. */
1384
+ additionalContexts?: readonly ContextCandidate[];
1381
1385
  cwd?: string;
1382
1386
  maxAttempts?: number;
1383
1387
  model: ModelConfig | ModelRef;
@@ -1938,6 +1942,7 @@ interface AgentRuntime$1 {
1938
1942
  contextWindow?: number;
1939
1943
  }): Promise<ContextStatus>;
1940
1944
  compactContext(agentId: AgentId, options?: {
1945
+ input?: UserInput;
1941
1946
  instructions?: string;
1942
1947
  idempotencyKey?: string;
1943
1948
  }): Promise<AgentRun>;
@@ -2029,6 +2034,7 @@ declare class AgentRuntime implements AgentRuntime$1 {
2029
2034
  contextWindow?: number;
2030
2035
  }): Promise<ContextStatus>;
2031
2036
  compactContext(agentId: AgentId, options?: {
2037
+ input?: UserInput;
2032
2038
  instructions?: string;
2033
2039
  idempotencyKey?: string;
2034
2040
  }): Promise<AgentRun>;
package/dist/index.js CHANGED
@@ -1751,7 +1751,7 @@ async function loadPhaseCode(codePath) {
1751
1751
  throw new Error(`Phase code at "${codePath}" must export a default function or a run() function.`);
1752
1752
  }
1753
1753
 
1754
- // src/harness/phases/default.ts
1754
+ // src/harness/phases/core-phases.ts
1755
1755
  var DEFAULT_PHASE_ID = "default";
1756
1756
  var STOP_PHASE_ID = "stop";
1757
1757
  var COMPACT_PHASE_ID = "compact";
@@ -1809,6 +1809,14 @@ function createCompactPhase() {
1809
1809
  });
1810
1810
  const tools = context.tools.filter((tool) => tool.core && (tool.name === "read" || tool.name === "bash"));
1811
1811
  const working = { ...context, tools, skills: [...context.skills], messages: [...context.messages] };
1812
+ const instructions = compactInstructions(context);
1813
+ if (instructions) {
1814
+ working.messages.push(createMessage("user", `Additional compaction instructions:
1815
+ ${instructions}`, {
1816
+ kind: "phase_input",
1817
+ phase: COMPACT_PHASE_ID
1818
+ }));
1819
+ }
1812
1820
  let result = await execution.invokeModel(working, { output: "internal" });
1813
1821
  for (let attempt = 0; attempt < 8 && result.toolCalls.length > 0; attempt += 1) {
1814
1822
  const calls = result.toolCalls.filter((call) => tools.some((tool) => tool.name === call.name));
@@ -1860,7 +1868,11 @@ function createCompactPhase() {
1860
1868
  return {
1861
1869
  route: "stop",
1862
1870
  phase: COMPACT_PHASE_ID,
1863
- payload: { kind: "context_compaction", summary: result.text },
1871
+ payload: {
1872
+ kind: "context_compaction",
1873
+ summary: result.text,
1874
+ ...instructions ? { instructions } : {}
1875
+ },
1864
1876
  status: {
1865
1877
  state: "completed",
1866
1878
  kind: "compacted",
@@ -1870,6 +1882,21 @@ function createCompactPhase() {
1870
1882
  }
1871
1883
  };
1872
1884
  }
1885
+ function compactInstructions(context) {
1886
+ const rowan = context.execution.runMetadata?.rowan;
1887
+ if (typeof rowan === "object" && rowan !== null && !Array.isArray(rowan)) {
1888
+ const configured = rowan.instructions;
1889
+ if (typeof configured === "string" && configured.trim().length > 0) {
1890
+ return configured.trim();
1891
+ }
1892
+ }
1893
+ const input = context.execution.input;
1894
+ if (input === void 0) return void 0;
1895
+ const text = typeof input === "string" ? input : input.filter((part) => part.type === "text").map((part) => part.text).join("");
1896
+ const match = text.match(/^\/compact(?:\s+([\s\S]*))?$/i);
1897
+ const instructions = match?.[1]?.trim();
1898
+ return instructions || void 0;
1899
+ }
1873
1900
  function createCorePhases() {
1874
1901
  return [createDefaultPhase(), createStopPhase(), createCompactPhase()];
1875
1902
  }
@@ -3378,6 +3405,10 @@ function assertAgentConfigRequest(config) {
3378
3405
  if (typeof context.name !== "string" || context.name.trim() === "") throw new TypeError("Context candidate name must be non-empty");
3379
3406
  assertJsonValue(context.value, `Context candidate "${context.name}" value`);
3380
3407
  }
3408
+ for (const context of config.additionalContexts ?? []) {
3409
+ if (typeof context.name !== "string" || context.name.trim() === "") throw new TypeError("Additional Context candidate name must be non-empty");
3410
+ assertJsonValue(context.value, `Additional Context candidate "${context.name}" value`);
3411
+ }
3381
3412
  }
3382
3413
  function assertToolExecutionResult(value) {
3383
3414
  if (!isToolResult(value)) throw new TypeError("Tool result must be JSON-safe and contain no Runtime identity");
@@ -3563,6 +3594,12 @@ function snapshotConfiguration(config) {
3563
3594
  name: context.name,
3564
3595
  value: snapshotJsonValue(context.value)
3565
3596
  })))
3597
+ } : {},
3598
+ ...config.additionalContexts ? {
3599
+ additionalContexts: Object.freeze(config.additionalContexts.map((context) => Object.freeze({
3600
+ name: context.name,
3601
+ value: snapshotJsonValue(context.value)
3602
+ })))
3566
3603
  } : {}
3567
3604
  });
3568
3605
  }
@@ -5474,14 +5511,18 @@ function resolveConfigurationSnapshot(registry, input) {
5474
5511
  throw new Error(`Agent Definition "${input.definition.name}" is not available in the Resource View.`);
5475
5512
  }
5476
5513
  const layer = input.definition.layer;
5477
- const definition = applyDefinitionLayer(base, layer);
5514
+ const selectedContexts = selectNamedResources(input.contexts ?? [], base.contexts, "Context");
5515
+ const contexts = appendAdditionalContexts(selectedContexts, input.additionalContexts ?? []);
5516
+ const definition = addAdditionalContextNames(
5517
+ applyDefinitionLayer(base, layer),
5518
+ contexts.slice(selectedContexts.length).map(({ name }) => name)
5519
+ );
5478
5520
  const tools = selectNamedResources(resolved.tools, definition.tools, "Tool");
5479
5521
  const skills = mergeSkills(
5480
5522
  selectNamedResources(resolved.skills, definition.skills, "Skill"),
5481
5523
  definition.bundledSkills
5482
5524
  );
5483
5525
  const phases = resolvePhases(resolved.phases, definition.phases);
5484
- const contexts = selectNamedResources(input.contexts ?? [], base.contexts, "Context");
5485
5526
  return {
5486
5527
  identity: input.identity,
5487
5528
  definition,
@@ -5539,6 +5580,22 @@ function applyDefinitionLayer(base, layer) {
5539
5580
  ...layer.phases === void 0 ? {} : { phases: intersectPhaseSelection(base.phases, layer.phases) }
5540
5581
  };
5541
5582
  }
5583
+ function appendAdditionalContexts(selected, additional) {
5584
+ const contexts = [...selected];
5585
+ const seen = new Set(contexts.map(({ name }) => name));
5586
+ for (const context of additional) {
5587
+ if (seen.has(context.name)) continue;
5588
+ seen.add(context.name);
5589
+ contexts.push(context);
5590
+ }
5591
+ return contexts;
5592
+ }
5593
+ function addAdditionalContextNames(definition, names) {
5594
+ if (definition.contexts === void 0 || names.length === 0) return definition;
5595
+ const existing = new Set(definition.contexts);
5596
+ const additions = names.filter((name) => !existing.has(name));
5597
+ return additions.length === 0 ? definition : { ...definition, contexts: [...definition.contexts, ...additions] };
5598
+ }
5542
5599
  function intersectNames(parent, layer, kind) {
5543
5600
  if (parent === void 0) return [...layer];
5544
5601
  const parentNames = new Set(parent);
@@ -5735,7 +5792,9 @@ var AgentRuntime = class _AgentRuntime {
5735
5792
  };
5736
5793
  const run = await this.owned.createRun({
5737
5794
  agentId,
5738
- input: "",
5795
+ // An empty input is a system-triggered Control Run. A non-empty input
5796
+ // represents a user invocation and is committed by the Durable Store.
5797
+ input: options.input ?? "",
5739
5798
  metadata,
5740
5799
  idempotencyKey: options.idempotencyKey ?? createId("compact")
5741
5800
  });
@@ -6002,17 +6061,6 @@ var AgentRuntime = class _AgentRuntime {
6002
6061
  ...executionContext2.phases,
6003
6062
  entryPhaseId: COMPACT_PHASE_ID
6004
6063
  };
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
6064
  }
6017
6065
  return executionContext2;
6018
6066
  };
@@ -6029,6 +6077,7 @@ ${instructions}`,
6029
6077
  agentId: run.agentId,
6030
6078
  runId: run.id,
6031
6079
  executionId: claim.execution.executionId,
6080
+ input: typeof run.input === "string" ? run.input : run.input.content,
6032
6081
  ...agent.metadata === void 0 ? {} : { agentMetadata: agent.metadata },
6033
6082
  ...run.metadata === void 0 ? {} : { runMetadata: run.metadata }
6034
6083
  },
@@ -6175,6 +6224,7 @@ ${instructions}`,
6175
6224
  const output = latestAssistant(run, result.messages.slice(modelMessages.length), modelMessages.length);
6176
6225
  if (controlKind === "compact" || isCompactionOutcome(result.outcome.payload)) {
6177
6226
  const summary = compactSummary(result.outcome.payload);
6227
+ const instructions = compactInstructions2(run) ?? compactOutputInstructions(result.outcome.payload);
6178
6228
  if (summary) {
6179
6229
  const covered = claim.history.at(-1);
6180
6230
  const record = {
@@ -6182,7 +6232,7 @@ ${instructions}`,
6182
6232
  agentId: run.agentId,
6183
6233
  summary,
6184
6234
  ...covered ? { coveredThrough: { messageId: covered.id, sequence: covered.sequenceWithinRun } } : {},
6185
- ...compactInstructions(run) ? { instructions: compactInstructions(run) } : {},
6235
+ ...instructions ? { instructions } : {},
6186
6236
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
6187
6237
  };
6188
6238
  await this.owned.commitContextCompaction(record);
@@ -6527,7 +6577,7 @@ function controlRunKind(run) {
6527
6577
  const kind = rowan.kind;
6528
6578
  return typeof kind === "string" ? kind : void 0;
6529
6579
  }
6530
- function compactInstructions(run) {
6580
+ function compactInstructions2(run) {
6531
6581
  const rowan = run.metadata?.rowan;
6532
6582
  if (typeof rowan !== "object" || rowan === null || !("instructions" in rowan)) return void 0;
6533
6583
  const instructions = rowan.instructions;
@@ -6538,6 +6588,11 @@ function compactSummary(payload) {
6538
6588
  const summary = payload.summary;
6539
6589
  return typeof summary === "string" && summary.trim().length > 0 ? summary : void 0;
6540
6590
  }
6591
+ function compactOutputInstructions(payload) {
6592
+ if (typeof payload !== "object" || payload === null || !("instructions" in payload)) return void 0;
6593
+ const instructions = payload.instructions;
6594
+ return typeof instructions === "string" && instructions.trim().length > 0 ? instructions.trim() : void 0;
6595
+ }
6541
6596
  function isCompactionOutcome(payload) {
6542
6597
  return typeof payload === "object" && payload !== null && "kind" in payload && payload.kind === "context_compaction";
6543
6598
  }
@@ -7012,7 +7067,7 @@ var InMemoryStore = class _InMemoryStore {
7012
7067
  }
7013
7068
  if (!run.pinnedConfigToken && input.configToken) run.pinnedConfigToken = input.configToken;
7014
7069
  let message;
7015
- if (!run.checkpoint && !run.initialMessageId && !isControlRun(run)) {
7070
+ if (!run.checkpoint && !run.initialMessageId && (!isControlRun(run) || hasUserInput(run.input))) {
7016
7071
  const userInput = normalizeUserInput(run.input);
7017
7072
  message = {
7018
7073
  id: input.messageId ?? createId("msg"),
@@ -7930,12 +7985,16 @@ function ownershipLost(expected, actual, reason) {
7930
7985
  function userInputContent(input) {
7931
7986
  return typeof input === "string" ? input : input.content;
7932
7987
  }
7988
+ function hasUserInput(input) {
7989
+ const content = userInputContent(input);
7990
+ return content.length > 0;
7991
+ }
7933
7992
  function userInputMetadata(input) {
7934
7993
  return typeof input === "string" ? void 0 : input.metadata;
7935
7994
  }
7936
7995
  function isControlRun(run) {
7937
7996
  const rowan = run.metadata?.rowan;
7938
- return typeof rowan === "object" && rowan !== null && "kind" in rowan && rowan.kind === "compact";
7997
+ return typeof rowan === "object" && rowan !== null && typeof rowan.kind === "string" && rowan.kind.length > 0;
7939
7998
  }
7940
7999
  function estimateMessageTokens(messages) {
7941
8000
  let characters = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rowan-agent/agent",
3
- "version": "0.9.10",
3
+ "version": "0.9.12",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -23,7 +23,7 @@
23
23
  "build": "tsup && bun scripts/check-public-interface.ts"
24
24
  },
25
25
  "dependencies": {
26
- "@rowan-agent/models": "0.6.5",
26
+ "@rowan-agent/models": "0.6.6",
27
27
  "jiti": "2.7.0",
28
28
  "typebox": "^1.0.0",
29
29
  "yaml": "^2.7.0"