@hunterzhu/pulse-runtime 0.5.1 → 0.7.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.
@@ -102,6 +102,20 @@ export class FileRuntimeContentStore {
102
102
  throw new Error('RUNTIME_CONTENT_REF_COLLISION');
103
103
  return structuredClone(envelope.value);
104
104
  }
105
+ loadSync(ref) {
106
+ let envelope;
107
+ try {
108
+ envelope = JSON.parse(readFileSync(this.pathFor(ref), 'utf8'));
109
+ }
110
+ catch (error) {
111
+ if (error.code === 'ENOENT')
112
+ return undefined;
113
+ throw error;
114
+ }
115
+ if (envelope.schemaVersion !== 1 || envelope.ref !== ref || envelope.value === undefined)
116
+ throw new Error('INVALID_RUNTIME_CONTENT');
117
+ return envelope.value;
118
+ }
105
119
  pathFor(ref) { return join(this.directory, `${createHash('sha256').update(ref).digest('hex')}.json`); }
106
120
  async readEnvelope(path) {
107
121
  try {
@@ -176,6 +190,10 @@ export class SqliteRuntimeContentStore {
176
190
  return structuredClone(JSON.parse(row.payload));
177
191
  });
178
192
  }
193
+ loadSync(ref) {
194
+ const row = this.open().prepare('SELECT payload FROM runtime_content WHERE namespace = ? AND ref = ?').get(this.namespace, ref);
195
+ return row && typeof row.payload === 'string' ? JSON.parse(row.payload) : undefined;
196
+ }
179
197
  async close() {
180
198
  await this.enqueue(async () => {
181
199
  this.database?.close();
@@ -888,8 +906,10 @@ export async function externalizeRuntimeResultBodies(snapshot, store) {
888
906
  const states = [copy.state, ...(copy.checkpoint === undefined ? [] : [copy.checkpoint.state])];
889
907
  for (const session of states) {
890
908
  for (const [ref, result] of session.state.results) {
891
- if (result.value === undefined)
909
+ if (result.value === undefined) {
910
+ refs.add(ref);
892
911
  continue;
912
+ }
893
913
  await store.save(ref, result.value);
894
914
  delete result.value;
895
915
  refs.add(ref);
@@ -52,6 +52,8 @@ export declare class SessionStoragePolicy {
52
52
  pin(key: string): void;
53
53
  unpin(key: string): void;
54
54
  remove(key: string): boolean;
55
+ /** Only after the event archive has durably acknowledged these exact events. */
56
+ forgetArchivedEvents(ids: Iterable<string>): void;
55
57
  /** Replace one logical owner’s pins without double-counting repeated reconciliation. */
56
58
  replacePinSource(source: string, keys: Iterable<string>): void;
57
59
  /** Make a private copy suitable for validate-stage storage admission. Shallow: records are immutable and shared. */
@@ -123,6 +123,14 @@ export class SessionStoragePolicy {
123
123
  }
124
124
  return true;
125
125
  }
126
+ /** Only after the event archive has durably acknowledged these exact events. */
127
+ forgetArchivedEvents(ids) {
128
+ for (const id of ids) {
129
+ const record = this.records.get(`event:${id}`);
130
+ if (record?.kind === 'event' && record.storageState === 'persisted' && record.pinCount === 0)
131
+ this.unset(record.key);
132
+ }
133
+ }
126
134
  /** Replace one logical owner’s pins without double-counting repeated reconciliation. */
127
135
  replacePinSource(source, keys) {
128
136
  const next = new Set(keys);
@@ -58,6 +58,7 @@ export interface RuntimeToolDefinition {
58
58
  execute(input: unknown, context: RuntimeToolContext): Promise<unknown> | unknown;
59
59
  executionRef?(input: unknown, context: RuntimeToolContext): JsonValue;
60
60
  resolveResources?(input: unknown): ResourceLockSpec[];
61
+ resolveTimeoutMs?(input: unknown): number;
61
62
  reconcile?(executionRef: JsonValue, context: RuntimeReconcileContext): Promise<RuntimeReconcileResult>;
62
63
  normalize?(output: unknown): JsonValue;
63
64
  summarize?(output: unknown): JsonValue;
@@ -299,7 +299,10 @@ export class RuntimeToolRegistry {
299
299
  admission(name, input) {
300
300
  const definition = this.require(name);
301
301
  const parsed = this.validateInput(name, input);
302
- return { locks: structuredClone(this.resolveResources(name, parsed)), sideEffectPolicy: definition.manifest.sideEffectPolicy, defaultTimeoutMs: definition.manifest.defaultTimeoutMs, retrySafety: definition.manifest.retrySafety, version: definition.manifest.version };
302
+ const timeout = definition.resolveTimeoutMs?.(parsed) ?? definition.manifest.defaultTimeoutMs;
303
+ if (definition.resolveTimeoutMs && (!Number.isSafeInteger(timeout) || timeout <= 0 || timeout > 600_000))
304
+ throw Object.assign(new Error('Resolved tool timeout is outside host limits.'), { code: 'INVALID_TOOL_TIMEOUT' });
305
+ return { locks: structuredClone(this.resolveResources(name, parsed)), sideEffectPolicy: definition.manifest.sideEffectPolicy, defaultTimeoutMs: timeout, retrySafety: definition.manifest.retrySafety, version: definition.manifest.version };
303
306
  }
304
307
  require(name) {
305
308
  if (!this.isAllowed(name))
@@ -619,6 +619,9 @@ function prepareLLMInput(state, lane, submission) {
619
619
  ? rawConversation
620
620
  : undefined;
621
621
  const conversationError = rawConversation !== undefined && conversation === undefined ? 'INVALID_LLM_CONVERSATION' : undefined;
622
+ const historySeqs = input.historySeqs;
623
+ if (historySeqs !== undefined && (!Array.isArray(historySeqs) || historySeqs.some(seq => typeof seq !== 'number' || !Number.isSafeInteger(seq) || seq < 1)))
624
+ return { error: 'INVALID_LLM_HISTORY_SELECTION' };
622
625
  const inputError = resultInputs.error ?? findingInputs.error ?? rejectedInputs.error ?? artifactInputs.error ?? eventInputs.error ?? conversationError;
623
626
  if (inputError)
624
627
  return { error: inputError };
@@ -628,7 +631,7 @@ function prepareLLMInput(state, lane, submission) {
628
631
  const agent = state.agents.get(lane.agentId);
629
632
  if (!agent)
630
633
  return { error: 'UNKNOWN_AGENT' };
631
- const projection = new ContextBuilder(state).build({ agent, lane, explicitContext: input.explicitContext === true, resultRefs, ...(artifactRefs.length ? { artifactRefs } : {}), eventIds: eventInputs.refs ?? [], ...(conversation === undefined || conversation.length === 0 ? {} : { conversation }), instruction, ...(typeof input.system === 'string' ? { system: input.system } : {}), ...(input.policy === undefined ? {} : { policy: input.policy }), ...(input.tools === undefined ? {} : { tools: input.tools }), toolSetId: typeof input.toolSetId === 'string' ? input.toolSetId : 'default' });
634
+ const projection = new ContextBuilder(state).build({ agent, lane, explicitContext: input.explicitContext === true, includeLaneHistory: input.includeLaneHistory === true, ...(Array.isArray(historySeqs) ? { historySeqs: historySeqs } : {}), resultRefs, ...(artifactRefs.length ? { artifactRefs } : {}), eventIds: eventInputs.refs ?? [], ...(conversation === undefined || conversation.length === 0 ? {} : { conversation }), instruction, ...(typeof input.system === 'string' ? { system: input.system } : {}), ...(input.policy === undefined ? {} : { policy: input.policy }), ...(input.tools === undefined ? {} : { tools: input.tools }), toolSetId: typeof input.toolSetId === 'string' ? input.toolSetId : 'default' });
632
635
  return { input: { ...input, request: projection } };
633
636
  }
634
637
  catch (cause) {
@@ -679,40 +682,42 @@ export function validateStep(state, laneId, output) {
679
682
  const seenToolCallIds = new Set();
680
683
  const seenCancelTargets = new Set();
681
684
  const cancelledSubtreeLanes = new Set();
682
- if (output.contextDelta !== undefined) {
683
- const deltaShapeError = validateContextDeltaShape(output.contextDelta);
685
+ if (output.laneContextDelta !== undefined && (output.contextDelta?.target !== 'global' || output.laneContextDelta?.target !== 'lane'))
686
+ return { rejection: error('INVALID_CONTEXT_DELTA_PAIR', 'A secondary lane delta requires a primary global delta') };
687
+ if (output.adoptCommittedContext && output.contextDelta?.target !== 'global')
688
+ return { rejection: error('INVALID_ADOPT_COMMITTED_CONTEXT', 'adoptCommittedContext requires a global ContextDelta') };
689
+ for (const delta of [output.contextDelta, output.laneContextDelta]) {
690
+ if (delta === undefined)
691
+ continue;
692
+ const deltaShapeError = validateContextDeltaShape(delta);
684
693
  if (deltaShapeError)
685
694
  return { rejection: error(deltaShapeError, 'ContextDelta shape is invalid') };
686
- const deltaPrivacyTaintError = validatePrivacyTaints(output.contextDelta.privacyTaints);
695
+ const deltaPrivacyTaintError = validatePrivacyTaints(delta.privacyTaints);
687
696
  if (deltaPrivacyTaintError)
688
697
  return { rejection: error(deltaPrivacyTaintError, 'ContextDelta privacy taints are invalid') };
689
- const deltaDerived = derivedPrivacy(state, lane, output.contextDelta.derivedFrom ?? []);
698
+ const deltaDerived = derivedPrivacy(state, lane, delta.derivedFrom ?? []);
690
699
  if (deltaDerived.error)
691
700
  return { rejection: error(deltaDerived.error, 'ContextDelta provenance references an unknown or invisible result') };
692
- if (output.contextDelta.privacy !== undefined && deltaDerived.privacy !== undefined && privacyRank(output.contextDelta.privacy) < privacyRank(deltaDerived.privacy))
701
+ if (delta.privacy !== undefined && deltaDerived.privacy !== undefined && privacyRank(delta.privacy) < privacyRank(deltaDerived.privacy))
693
702
  return { rejection: error('PRIVACY_DOWNGRADE_WITHOUT_PROOF', 'ContextDelta privacy cannot be broader than its sources') };
694
- if (output.contextDelta.target === 'global' && !output.contextDelta.proposal && lane.ownerLaneId !== undefined)
703
+ if (delta.target === 'global' && !delta.proposal && lane.ownerLaneId !== undefined)
695
704
  return { rejection: error('GLOBAL_CONTEXT_WRITE_NOT_AUTHORIZED', 'Only the root Lane may commit Global Context directly.') };
696
- if (output.contextDelta.target !== 'global' && output.contextDelta.proposal)
705
+ if (delta.target !== 'global' && delta.proposal)
697
706
  return { rejection: error('INVALID_MERGE_PROPOSAL', 'Only Global Context deltas may be proposals.') };
698
- const applied = applyContextDelta(state, workingLane, output.contextDelta, mutations, output.contextDelta.proposal ? `proposal-${proposalCounter++}` : undefined);
707
+ const applied = applyContextDelta(state, workingLane, delta, mutations, delta.proposal ? `proposal-${proposalCounter++}` : undefined);
699
708
  if (applied.error)
700
709
  return { rejection: error(applied.error, 'ContextDelta rejected') };
701
- if (output.contextDelta.target === 'lane') {
710
+ if (delta.target === 'lane') {
702
711
  const nextValue = mutations[mutations.length - 1];
703
712
  if (nextValue?.op === 'setLaneContext')
704
713
  workingLane.context = { ...workingLane.context, state: nextValue.value, version: applied.nextVersion, ...(nextValue.history === undefined ? {} : { history: structuredClone(nextValue.history) }), ...(nextValue.metadata === undefined ? {} : structuredClone(nextValue.metadata)) };
705
714
  }
706
- if (output.adoptCommittedContext && output.contextDelta.target !== 'global')
707
- return { rejection: error('INVALID_ADOPT_COMMITTED_CONTEXT', 'adoptCommittedContext requires a global ContextDelta') };
708
- if (output.contextDelta.proposal && output.adoptCommittedContext)
715
+ if (delta.proposal && output.adoptCommittedContext)
709
716
  return { rejection: error('INVALID_ADOPT_COMMITTED_CONTEXT', 'proposals cannot be adopted in the same transaction') };
710
- if (output.contextDelta.target === 'global' && output.adoptCommittedContext)
717
+ if (delta.target === 'global' && output.adoptCommittedContext)
711
718
  workingLane.contextSnapshotVersion = applied.nextVersion;
712
719
  }
713
- else if (output.adoptCommittedContext)
714
- return { rejection: error('INVALID_ADOPT_COMMITTED_CONTEXT', 'adoptCommittedContext requires a ContextDelta') };
715
- const compactRequested = output.contextDelta?.target === 'lane' && output.contextDelta.ops.some((op) => op.op === 'compact_history');
720
+ const compactRequested = [output.contextDelta, output.laneContextDelta].some(delta => delta?.target === 'lane' && delta.ops.some(op => op.op === 'compact_history'));
716
721
  // ReAct can discover pressure only after consuming a model result. Permit
717
722
  // the two-step compaction handoff and its summary request to cross the hard
718
723
  // threshold; the following compact_history delta removes the old records.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hunterzhu/pulse-runtime",
3
- "version": "0.5.1",
3
+ "version": "0.7.0",
4
4
  "license": "PolyForm-Noncommercial-1.0.0",
5
5
  "repository": {
6
6
  "type": "git",