@hunterzhu/pulse-runtime 0.1.5 → 0.1.7
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 +6 -1
- package/dist/context/builder.js +37 -4
- package/dist/core/inbox.d.ts +6 -0
- package/dist/core/inbox.js +31 -7
- package/dist/core/mutations.d.ts +5 -1
- package/dist/core/mutations.js +5 -1
- package/dist/core/types.d.ts +26 -1
- package/dist/core/types.js +1 -1
- package/dist/dsl/program.d.ts +4 -1
- package/dist/dsl/program.js +147 -24
- package/dist/dsl/session.d.ts +7 -0
- package/dist/dsl/session.js +37 -10
- package/dist/dsl/templates.d.ts +2 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/scheduler/human-arbitration.d.ts +59 -0
- package/dist/scheduler/human-arbitration.js +62 -0
- package/dist/scheduler/runtime.d.ts +51 -3
- package/dist/scheduler/runtime.js +445 -40
- package/dist/scheduler/worker.d.ts +2 -0
- package/dist/scheduler/worker.js +24 -4
- package/dist/storage/session.d.ts +2 -1
- package/dist/storage/session.js +14 -0
- package/dist/transitions/validate.js +14 -4
- package/package.json +1 -1
|
@@ -85,6 +85,8 @@ export declare class WorkerCoordinator implements WorkerCoordinatorContract {
|
|
|
85
85
|
private readonly persistenceBackend;
|
|
86
86
|
private persistenceDigest;
|
|
87
87
|
private persistencePending;
|
|
88
|
+
private persistenceDirty;
|
|
89
|
+
private persistenceScheduled;
|
|
88
90
|
private sequence;
|
|
89
91
|
constructor(options?: WorkerCoordinatorOptions);
|
|
90
92
|
static restore(snapshot: WorkerCoordinatorSnapshot, options?: WorkerCoordinatorOptions): WorkerCoordinator;
|
package/dist/scheduler/worker.js
CHANGED
|
@@ -173,6 +173,8 @@ export class WorkerCoordinator {
|
|
|
173
173
|
persistenceBackend;
|
|
174
174
|
persistenceDigest;
|
|
175
175
|
persistencePending = Promise.resolve();
|
|
176
|
+
persistenceDirty = false;
|
|
177
|
+
persistenceScheduled = false;
|
|
176
178
|
sequence = 1;
|
|
177
179
|
constructor(options = {}) { this.persistenceBackend = options.persistenceBackend; }
|
|
178
180
|
static restore(snapshot, options = {}) {
|
|
@@ -349,10 +351,28 @@ export class WorkerCoordinator {
|
|
|
349
351
|
schedulePersistence() {
|
|
350
352
|
if (!this.persistenceBackend)
|
|
351
353
|
return;
|
|
354
|
+
this.persistenceDirty = true;
|
|
355
|
+
if (this.persistenceScheduled)
|
|
356
|
+
return;
|
|
357
|
+
this.persistenceScheduled = true;
|
|
352
358
|
const operation = this.persistencePending.catch(() => undefined).then(async () => {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
359
|
+
while (this.persistenceDirty) {
|
|
360
|
+
this.persistenceDirty = false;
|
|
361
|
+
const snapshot = this.snapshot();
|
|
362
|
+
const digest = snapshot.integrity?.digest;
|
|
363
|
+
if (digest === this.persistenceDigest)
|
|
364
|
+
continue;
|
|
365
|
+
try {
|
|
366
|
+
await this.persistenceBackend.save(snapshot, this.persistenceDigest);
|
|
367
|
+
this.persistenceDigest = digest;
|
|
368
|
+
}
|
|
369
|
+
catch (cause) {
|
|
370
|
+
this.persistenceDirty = true;
|
|
371
|
+
throw cause;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}).finally(() => {
|
|
375
|
+
this.persistenceScheduled = false;
|
|
356
376
|
});
|
|
357
377
|
this.persistencePending = operation;
|
|
358
378
|
}
|
|
@@ -668,7 +688,7 @@ export class SqliteDistributedWorkerCoordinator {
|
|
|
668
688
|
result.reject(task.error ?? { code: 'WORKER_FAILED', message: 'WORKER_FAILED' });
|
|
669
689
|
else
|
|
670
690
|
result.reject(new Error('WORKER_CANCELLED'));
|
|
671
|
-
},
|
|
691
|
+
}, 50);
|
|
672
692
|
watcher.unref();
|
|
673
693
|
this.watchers.set(taskId, watcher);
|
|
674
694
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentRecord, ArtifactRecord, EffectRecord, JsonValue, LaneRecord, MergeProposal, PrivacyLabel, PrivacyMetadata, ResultRecord, RuntimeEvent, RuntimeState, WaitRecord, ToolCallCorrelation } from '../core/types.js';
|
|
1
|
+
import type { AgentRecord, ArtifactRecord, EffectRecord, JsonValue, LaneRecord, MergeProposal, PrivacyLabel, PrivacyMetadata, ResultRecord, RuntimeEvent, RuntimeState, WaitRecord, ToolCallCorrelation, HumanInputRecord } from '../core/types.js';
|
|
2
2
|
export interface SessionSnapshot {
|
|
3
3
|
schemaVersion: 1;
|
|
4
4
|
state: {
|
|
@@ -18,6 +18,7 @@ export interface SessionSnapshot {
|
|
|
18
18
|
artifacts?: Array<[string, ArtifactRecord]>;
|
|
19
19
|
toolCallCorrelations?: Array<[string, ToolCallCorrelation]>;
|
|
20
20
|
mergeProposals: Array<[string, MergeProposal]>;
|
|
21
|
+
humanInputs?: Array<[string, HumanInputRecord]>;
|
|
21
22
|
events: RuntimeEvent[];
|
|
22
23
|
eventsCompactedThrough?: number;
|
|
23
24
|
nextIds: RuntimeState['nextIds'];
|
package/dist/storage/session.js
CHANGED
|
@@ -304,6 +304,17 @@ function validateStateConfiguration(value) {
|
|
|
304
304
|
throw new Error('INVALID_SESSION_SNAPSHOT');
|
|
305
305
|
if (value.trustedSanitizerIds !== undefined && (!Array.isArray(value.trustedSanitizerIds) || new Set(value.trustedSanitizerIds).size !== value.trustedSanitizerIds.length || value.trustedSanitizerIds.some((id) => typeof id !== 'string' || id.length === 0)))
|
|
306
306
|
throw new Error('INVALID_SESSION_SNAPSHOT');
|
|
307
|
+
if (value.humanInputs !== undefined) {
|
|
308
|
+
const ids = new Set();
|
|
309
|
+
for (const entry of value.humanInputs) {
|
|
310
|
+
if (!Array.isArray(entry) || entry.length !== 2 || typeof entry[0] !== 'string' || entry[0].length === 0 || ids.has(entry[0]))
|
|
311
|
+
throw new Error('INVALID_SESSION_SNAPSHOT');
|
|
312
|
+
const input = entry[1];
|
|
313
|
+
if (!input || input.id !== entry[0] || typeof input.agentId !== 'string' || input.agentId.length === 0 || !['pending', 'consumed', 'deferred'].includes(input.status) || typeof input.receivedAt !== 'number' || !Number.isFinite(input.receivedAt))
|
|
314
|
+
throw new Error('INVALID_SESSION_SNAPSHOT');
|
|
315
|
+
ids.add(entry[0]);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
307
318
|
}
|
|
308
319
|
export function exportRuntimeState(state) {
|
|
309
320
|
return {
|
|
@@ -318,6 +329,7 @@ export function exportRuntimeState(state) {
|
|
|
318
329
|
artifacts: [...state.artifacts.entries()].map(([ref, artifact]) => [ref, structuredClone(artifact)]),
|
|
319
330
|
toolCallCorrelations: [...state.toolCallCorrelations.entries()].map(([id, correlation]) => [id, structuredClone(correlation)]),
|
|
320
331
|
mergeProposals: [...state.mergeProposals.entries()].map(([id, proposal]) => [id, structuredClone(proposal)]),
|
|
332
|
+
humanInputs: [...state.humanInputs.entries()].map(([id, input]) => [id, structuredClone(input)]),
|
|
321
333
|
events: state.events.map((event) => normalizeRuntimeEvent(event, event.seq, { sessionId: event.sessionId, timestamp: event.timestamp })),
|
|
322
334
|
...(state.eventsCompactedThrough === undefined ? {} : { eventsCompactedThrough: state.eventsCompactedThrough }),
|
|
323
335
|
nextIds: { ...state.nextIds },
|
|
@@ -440,6 +452,8 @@ export function importRuntimeState(snapshot) {
|
|
|
440
452
|
state.toolCallCorrelations.set(id, structuredClone(correlation));
|
|
441
453
|
for (const [id, proposal] of value.state.mergeProposals ?? [])
|
|
442
454
|
state.mergeProposals.set(id, structuredClone(proposal));
|
|
455
|
+
for (const [id, input] of value.state.humanInputs ?? [])
|
|
456
|
+
state.humanInputs.set(id, structuredClone(input));
|
|
443
457
|
state.events = value.state.events.map((event) => normalizeRuntimeEvent(event, event.seq, { sessionId: event.sessionId, timestamp: event.timestamp }));
|
|
444
458
|
if (value.state.eventsCompactedThrough !== undefined)
|
|
445
459
|
state.eventsCompactedThrough = value.state.eventsCompactedThrough;
|
|
@@ -7,7 +7,7 @@ import { ContextBuilder, contentHash, estimateHistoryTokens, hasUnsafePathSegmen
|
|
|
7
7
|
const isLocal = (value) => 'local' in value;
|
|
8
8
|
const clone = (value) => structuredClone(value);
|
|
9
9
|
const resultMetadata = (value) => ({ sizeBytes: Buffer.byteLength(stableSerialize(value), 'utf8'), contentHash: contentHash(value) });
|
|
10
|
-
const laneCopy = (lane) => ({ ...lane, resume: clone(lane.resume), context: clone(lane.context), ...(lane.visibleResultRefs === undefined ? {} : { visibleResultRefs: new Set(lane.visibleResultRefs) }), children: new Set(lane.children), ownedEffectIds: new Set(lane.ownedEffectIds), ...(lane.pendingResumeInput === undefined ? {} : { pendingResumeInput: clone(lane.pendingResumeInput) }), ...(lane.pendingControlProposals === undefined ? {} : { pendingControlProposals: clone(lane.pendingControlProposals) }), ...(lane.pendingOutcome === undefined ? {} : { pendingOutcome: clone(lane.pendingOutcome) }) });
|
|
10
|
+
const laneCopy = (lane) => ({ ...lane, resume: clone(lane.resume), context: clone(lane.context), ...(lane.visibleResultRefs === undefined ? {} : { visibleResultRefs: new Set(lane.visibleResultRefs) }), children: new Set(lane.children), ownedEffectIds: new Set(lane.ownedEffectIds), ...(lane.pendingResumeInput === undefined ? {} : { pendingResumeInput: clone(lane.pendingResumeInput) }), ...(lane.pendingHumanInputs === undefined ? {} : { pendingHumanInputs: clone(lane.pendingHumanInputs) }), ...(lane.pendingControlProposals === undefined ? {} : { pendingControlProposals: clone(lane.pendingControlProposals) }), ...(lane.pendingOutcome === undefined ? {} : { pendingOutcome: clone(lane.pendingOutcome) }) });
|
|
11
11
|
function isRuntimeJsonValue(value, seen = new Set()) {
|
|
12
12
|
if (value === null || typeof value === 'string' || typeof value === 'boolean')
|
|
13
13
|
return true;
|
|
@@ -614,7 +614,12 @@ function prepareLLMInput(state, lane, submission) {
|
|
|
614
614
|
const rejectedInputs = readRefs('rejectedOutputRefs');
|
|
615
615
|
const artifactInputs = readRefs('artifacts');
|
|
616
616
|
const eventInputs = readRefs('events');
|
|
617
|
-
const
|
|
617
|
+
const rawConversation = rawInputs.conversation;
|
|
618
|
+
const conversation = rawConversation === undefined ? [] : Array.isArray(rawConversation) && rawConversation.every((item) => item && typeof item === 'object' && !Array.isArray(item) && item.role !== undefined && ['system', 'user', 'assistant'].includes(String(item.role)) && typeof item.content === 'string')
|
|
619
|
+
? rawConversation
|
|
620
|
+
: undefined;
|
|
621
|
+
const conversationError = rawConversation !== undefined && conversation === undefined ? 'INVALID_LLM_CONVERSATION' : undefined;
|
|
622
|
+
const inputError = resultInputs.error ?? findingInputs.error ?? rejectedInputs.error ?? artifactInputs.error ?? eventInputs.error ?? conversationError;
|
|
618
623
|
if (inputError)
|
|
619
624
|
return { error: inputError };
|
|
620
625
|
const resultRefs = [...new Set([...(resultInputs.refs ?? []), ...(findingInputs.refs ?? []), ...(rejectedInputs.refs ?? [])])];
|
|
@@ -623,7 +628,7 @@ function prepareLLMInput(state, lane, submission) {
|
|
|
623
628
|
const agent = state.agents.get(lane.agentId);
|
|
624
629
|
if (!agent)
|
|
625
630
|
return { error: 'UNKNOWN_AGENT' };
|
|
626
|
-
const projection = new ContextBuilder(state).build({ agent, lane, resultRefs, ...(artifactRefs.length ? { artifactRefs } : {}), eventIds: eventInputs.refs ?? [], 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' });
|
|
631
|
+
const projection = new ContextBuilder(state).build({ agent, lane, 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' });
|
|
627
632
|
return { input: { ...input, request: projection } };
|
|
628
633
|
}
|
|
629
634
|
catch (cause) {
|
|
@@ -708,8 +713,13 @@ export function validateStep(state, laneId, output) {
|
|
|
708
713
|
else if (output.adoptCommittedContext)
|
|
709
714
|
return { rejection: error('INVALID_ADOPT_COMMITTED_CONTEXT', 'adoptCommittedContext requires a ContextDelta') };
|
|
710
715
|
const compactRequested = output.contextDelta?.target === 'lane' && output.contextDelta.ops.some((op) => op.op === 'compact_history');
|
|
716
|
+
// ReAct can discover pressure only after consuming a model result. Permit
|
|
717
|
+
// the two-step compaction handoff and its summary request to cross the hard
|
|
718
|
+
// threshold; the following compact_history delta removes the old records.
|
|
719
|
+
const compactionHandoff = output.next.step === '$compact:summarize' && actions.length === 0;
|
|
720
|
+
const compactionSummarySubmission = actions.some((action) => action.type === 'submit_effects' && action.effects.some((effect) => effect.key === '$compact-summary'));
|
|
711
721
|
const nextHistoryTokens = estimateHistoryTokens(workingLane.context.history);
|
|
712
|
-
if (nextHistoryTokens > state.historyHardTokens && !compactRequested)
|
|
722
|
+
if (nextHistoryTokens > state.historyHardTokens && !compactRequested && !compactionHandoff && !compactionSummarySubmission)
|
|
713
723
|
return { rejection: error('CONTEXT_TOO_LARGE', 'Lane history exceeded hardTokens and must be compacted before another Step can commit.', { historyTokens: nextHistoryTokens, softTokens: state.historySoftTokens, hardTokens: state.historyHardTokens }) };
|
|
714
724
|
const pressure = historyPressure(workingLane.context.history, state.historySoftTokens, state.historyHardTokens);
|
|
715
725
|
if (pressure)
|