@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.
@@ -1,6 +1,6 @@
1
1
  import { MutationLog } from '../storage/mutation-log.js';
2
2
  import { PriorityInheritance, ReadyQueue, type RuntimeClock } from './index.js';
3
- import type { EffectRecord, EffectSubmission, EffectState, JsonValue, LaneRecord, LaneStepOutput, Outcome, ResumeInput, RuntimeState, RuntimeError, ForkAffinityMode, PrivacyLabel, PrivacyTaint, ProvenanceRef, ResumePoint, ResultRecord } from '../core/types.js';
3
+ import type { EffectRecord, EffectSubmission, EffectState, JsonValue, LaneRecord, LaneStepOutput, Outcome, ResumeInput, RuntimeState, RuntimeError, ForkAffinityMode, PrivacyLabel, PrivacyTaint, ProvenanceRef, ResumePoint, ResultRecord, HumanInputRecord } from '../core/types.js';
4
4
  import { QuarantineScope } from '../lifecycle/scopes.js';
5
5
  import { PulseSession } from '../dsl/session.js';
6
6
  import type { ProgramRef } from '../dsl/templates.js';
@@ -18,10 +18,12 @@ import { type ArtifactPublication } from '../storage/artifacts.js';
18
18
  import { type FindingPublication } from '../storage/findings.js';
19
19
  import { RuntimeToolRegistry } from '../tools/registry.js';
20
20
  import { type SchedulerDecisionConfig } from './decision.js';
21
+ import { type HumanArbitrationConfig } from './human-arbitration.js';
21
22
  export interface LaneStepContext {
22
23
  lane: Readonly<LaneRecord>;
23
24
  state: Readonly<RuntimeState>;
24
25
  resumeInput?: ResumeInput;
26
+ humanInputs?: readonly HumanInputRecord[];
25
27
  now: number;
26
28
  observe?: (event: {
27
29
  type: 'progress' | 'chunk' | 'trace' | 'warning' | 'diagnostic';
@@ -83,6 +85,12 @@ export type HostCommand = {
83
85
  agentId: string;
84
86
  effectId: string;
85
87
  value: JsonValue;
88
+ } | {
89
+ type: 'human_input';
90
+ agentId: string;
91
+ inputId: string;
92
+ value: JsonValue;
93
+ targetEffectId?: string;
86
94
  } | {
87
95
  type: 'cancel';
88
96
  agentId: string;
@@ -132,7 +140,12 @@ interface SchedulerDecisionFact {
132
140
  orderedLaneIds: string[];
133
141
  modelId: string;
134
142
  }
135
- type RuntimeFact = HostCommand | EffectCompletionFact | LLMPreparationFact | EffectReconcileFact | SchedulerDecisionFact;
143
+ interface HumanArbitrationFact {
144
+ [key: string]: JsonValue;
145
+ type: 'human_arbitration';
146
+ decision: JsonValue;
147
+ }
148
+ type RuntimeFact = HostCommand | EffectCompletionFact | LLMPreparationFact | EffectReconcileFact | SchedulerDecisionFact | HumanArbitrationFact;
136
149
  export interface RuntimeConfig {
137
150
  maxLaneStepsPerTick?: number;
138
151
  maxTickMs?: number;
@@ -187,6 +200,8 @@ export interface RuntimeConfig {
187
200
  persistenceExpectedDigest?: string;
188
201
  budget?: RuntimeBudgetConfig;
189
202
  schedulerDecision?: SchedulerDecisionConfig;
203
+ /** Optional model used for ordinary Human messages after deterministic control rules. */
204
+ humanArbitration?: HumanArbitrationConfig;
190
205
  }
191
206
  export interface RuntimeBudgetConfig {
192
207
  maxTotalAttempts?: number;
@@ -263,6 +278,13 @@ export declare class PulseRuntime {
263
278
  private readonly auditLogPrivacy;
264
279
  private readonly persistenceBackend;
265
280
  private readonly sessionStore;
281
+ /**
282
+ * The file session store is synchronous by design, so avoid rewriting an
283
+ * unchanged warm-start snapshot on every scheduler tick. Without this
284
+ * guard an idle run can spend its whole event loop serializing and fsyncing
285
+ * the same snapshot, starving effect dispatch and human input handling.
286
+ */
287
+ private readonly sessionStoreDigests;
266
288
  private readonly enforcingRecoveryPrograms;
267
289
  private readonly toolVersions;
268
290
  private readonly recoveryCompatibility;
@@ -305,6 +327,7 @@ export declare class PulseRuntime {
305
327
  private factInboxDedupeArchive;
306
328
  private readonly customExecutor;
307
329
  private readonly builtinHumanEffects;
330
+ private humanInputProgram;
308
331
  private enqueueSeq;
309
332
  private readonly maxSteps;
310
333
  private readonly maxTickMs;
@@ -317,6 +340,10 @@ export declare class PulseRuntime {
317
340
  private readonly maxPreparedLLMs;
318
341
  private readonly effectSubmissionPreparer;
319
342
  private readonly schedulerDecisionCoordinator;
343
+ private readonly humanArbitrationCoordinator;
344
+ private readonly humanArbitrationModelId;
345
+ private humanArbitrationRequestSeq;
346
+ private readonly humanArbitrationRequests;
320
347
  private readonly schedulerDecisionConfig;
321
348
  private readonly schedulerDecisionModelId;
322
349
  private readonly schedulerDecisionMaxReorderDistance;
@@ -331,16 +358,34 @@ export declare class PulseRuntime {
331
358
  private readonly sessionId;
332
359
  private hostCommandSeq;
333
360
  private factWaiters;
361
+ private activityWaiters;
334
362
  private wakeScheduled;
363
+ /**
364
+ * A lock grant can happen synchronously while an effect completion is being
365
+ * applied inside tick(). scheduleWake() intentionally avoids re-entering the
366
+ * drain in that case, so remember forced wake requests and service them once
367
+ * the current drain has finished.
368
+ */
369
+ private wakeAfterDrain;
335
370
  private inDrain;
336
371
  private wakeError;
337
372
  private tickBudget;
338
373
  static restore(backend: RuntimePersistenceBackend, config?: Omit<RuntimeConfig, 'persistence'>): Promise<PulseRuntime>;
339
374
  constructor(config?: RuntimeConfig);
340
375
  register(program: LaneProgram): void;
376
+ /** Register the program used for urgent, concurrent human interactions. */
377
+ setHumanInputProgram(program: LaneProgram): void;
341
378
  createAgent(request: AgentCreateRequest): AgentHandle;
342
379
  createAgent(goal: string, program: LaneProgram, agentId?: string): AgentHandle;
343
380
  start(agentId: string): PulseSession;
381
+ /** Accept external human input without waiting for the current Effect to settle. */
382
+ submitHumanInput(agentId: string, inputId: string, value: JsonValue, targetEffectId?: string): boolean;
383
+ humanInputsFor(agentId: string): HumanInputRecord[];
384
+ private humanArbitrationRequest;
385
+ private ownsAgentForRuntime;
386
+ private commitHumanInputDecision;
387
+ private applyHumanArbitrationDecision;
388
+ private requestHumanArbitration;
344
389
  requestCancel(agentId: string, reason?: string): void;
345
390
  setLanePriority(laneId: string, priority: number): void;
346
391
  effectHandle(effectId: string): EffectHandle;
@@ -369,7 +414,7 @@ export declare class PulseRuntime {
369
414
  private executeRegisteredEffect;
370
415
  private journalEffect;
371
416
  private enqueueFact;
372
- enqueueHostCommand(command: HostCommand): void;
417
+ enqueueHostCommand(command: HostCommand): boolean;
373
418
  private enqueueEffectCompletion;
374
419
  private enqueueLLMPreparation;
375
420
  private enqueueEffectReconcile;
@@ -417,6 +462,9 @@ export declare class PulseRuntime {
417
462
  private hasPendingTickCleanup;
418
463
  private hasPendingHostInteraction;
419
464
  private waitForFact;
465
+ /** Wait for a state or observation change without polling the event loop. */
466
+ waitForActivity(timeoutMs?: number): Promise<void>;
467
+ private notifyActivity;
420
468
  private completeFinishedChildAgents;
421
469
  completeEffect(effectId: string, execution: EffectExecution, status?: 'succeeded' | 'failed' | 'cancelled', error?: RuntimeError, additionalMutations?: Mutation[]): boolean;
422
470
  markRemoteUnknown(effectId: string, sideEffectState: 'none' | 'applied' | 'known' | 'unknown'): void;