@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.
- package/dist/context/builder.d.ts +3 -0
- package/dist/context/builder.js +47 -8
- package/dist/core/tool-image.d.ts +9 -0
- package/dist/core/tool-image.js +13 -0
- package/dist/core/types.d.ts +10 -0
- package/dist/dsl/evidence-reuse.d.ts +11 -0
- package/dist/dsl/evidence-reuse.js +50 -0
- package/dist/dsl/index.d.ts +1 -0
- package/dist/dsl/index.js +1 -0
- package/dist/dsl/patch-recovery.d.ts +33 -0
- package/dist/dsl/patch-recovery.js +51 -0
- package/dist/dsl/program.d.ts +33 -0
- package/dist/dsl/program.js +480 -104
- package/dist/dsl/read-continuation.d.ts +9 -0
- package/dist/dsl/read-continuation.js +84 -0
- package/dist/dsl/templates.d.ts +7 -0
- package/dist/dsl/templates.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/models/router.d.ts +2 -0
- package/dist/models/router.js +2 -2
- package/dist/scheduler/runtime.d.ts +16 -1
- package/dist/scheduler/runtime.js +155 -10
- package/dist/storage/persistence.d.ts +11 -0
- package/dist/storage/persistence.js +21 -1
- package/dist/storage/policy.d.ts +2 -0
- package/dist/storage/policy.js +8 -0
- package/dist/tools/registry.d.ts +1 -0
- package/dist/tools/registry.js +4 -1
- package/dist/transitions/validate.js +22 -17
- package/package.json +1 -1
|
@@ -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);
|
package/dist/storage/policy.d.ts
CHANGED
|
@@ -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. */
|
package/dist/storage/policy.js
CHANGED
|
@@ -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);
|
package/dist/tools/registry.d.ts
CHANGED
|
@@ -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;
|
package/dist/tools/registry.js
CHANGED
|
@@ -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
|
-
|
|
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 !==
|
|
683
|
-
|
|
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(
|
|
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,
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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,
|
|
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 (
|
|
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 (
|
|
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 (
|
|
717
|
+
if (delta.target === 'global' && output.adoptCommittedContext)
|
|
711
718
|
workingLane.contextSnapshotVersion = applied.nextVersion;
|
|
712
719
|
}
|
|
713
|
-
|
|
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.
|