pi-crew 0.9.68 → 0.10.2
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/CHANGELOG.md +222 -0
- package/NOTICE.md +21 -0
- package/README.md +44 -2
- package/agents/analyst.md +1 -1
- package/agents/cold-verifier.md +3 -1
- package/agents/critic.md +1 -1
- package/agents/executor.md +1 -1
- package/agents/explorer.md +1 -1
- package/agents/planner.md +1 -1
- package/agents/reviewer.md +1 -1
- package/agents/security-reviewer.md +1 -1
- package/agents/test-engineer.md +1 -1
- package/agents/verifier.md +1 -1
- package/agents/writer.md +1 -1
- package/dist/index.mjs +68113 -60774
- package/docs/README.md +2 -0
- package/docs/actions-reference.md +31 -0
- package/docs/commands-reference.md +17 -6
- package/docs/resource-formats.md +13 -0
- package/package.json +4 -2
- package/schema.json +503 -91
- package/scripts/resource-sampler.mjs +36 -2
- package/skills/requirements-to-task-packet/SKILL.md +26 -0
- package/skills/widget-rendering/SKILL.md +7 -7
- package/src/agents/agent-config.ts +2 -1
- package/src/agents/discover-agents.ts +23 -14
- package/src/config/config-merge.ts +183 -0
- package/src/config/config-validation.ts +687 -0
- package/src/config/config.ts +22 -864
- package/src/config/defaults.ts +43 -2
- package/src/config/drift-detector.ts +1 -1
- package/src/config/env-vars.ts +691 -0
- package/src/config/role-tools.ts +11 -9
- package/src/config/sanitize-project-config.ts +172 -0
- package/src/config/types.ts +49 -1
- package/src/extension/async-notifier.ts +25 -2
- package/src/extension/crew-cleanup.ts +13 -0
- package/src/extension/crew-vibes/config.ts +2 -1
- package/src/extension/crew-vibes/footer.ts +19 -0
- package/src/extension/crew-vibes/index.ts +11 -1
- package/src/extension/plan-orchestrate.ts +132 -0
- package/src/extension/register.ts +8 -0
- package/src/extension/registration/command-registration.ts +1 -0
- package/src/extension/registration/commands/dashboard.ts +158 -0
- package/src/extension/registration/commands/index.ts +35 -0
- package/src/extension/registration/commands/manage.ts +303 -0
- package/src/extension/registration/commands/run.ts +228 -0
- package/src/extension/registration/commands/shared.ts +639 -0
- package/src/extension/registration/commands/status.ts +60 -0
- package/src/extension/registration/commands.ts +13 -1224
- package/src/extension/registration/foreground-run-controller.ts +10 -2
- package/src/extension/registration/lifecycle-handlers.ts +178 -17
- package/src/extension/registration/runtime-cleanup.ts +23 -5
- package/src/extension/registration/subagent-tools.ts +218 -9
- package/src/extension/registration/team-tool.ts +5 -1
- package/src/extension/registration/ui.ts +5 -4
- package/src/extension/rpc-hmac.ts +5 -3
- package/src/extension/team-tool/api/heartbeat.ts +47 -10
- package/src/extension/team-tool/api/plan-approval.ts +9 -0
- package/src/extension/team-tool/api/task-claims.ts +109 -40
- package/src/extension/team-tool/cancel.ts +84 -50
- package/src/extension/team-tool/dispatch/index.ts +1 -0
- package/src/extension/team-tool/dispatch/run.ts +4 -1
- package/src/extension/team-tool/doctor.ts +103 -1
- package/src/extension/team-tool/orchestrate.ts +66 -1
- package/src/extension/team-tool/plans.ts +192 -0
- package/src/extension/team-tool/respond.ts +197 -65
- package/src/extension/team-tool/run-deadline.ts +35 -3
- package/src/extension/team-tool/run-intent.ts +63 -0
- package/src/extension/team-tool/run.ts +74 -20
- package/src/extension/team-tool/status.ts +84 -26
- package/src/extension/team-tool.ts +11 -2
- package/src/hooks/registry.ts +1 -6
- package/src/i18n.ts +9 -0
- package/src/prompt/prompt-runtime.ts +521 -2
- package/src/prompt/worker-events-channel.ts +173 -0
- package/src/runtime/README.md +8 -8
- package/src/runtime/async-runner.ts +7 -3
- package/src/runtime/background-runner.ts +42 -14
- package/src/runtime/broker/broker-issuer.ts +9 -2
- package/src/runtime/broker/crew-broker-tokens.ts +43 -6
- package/src/runtime/broker/crew-broker.ts +838 -10
- package/src/runtime/broker/wait-status-cache.ts +157 -0
- package/src/runtime/budget-enforcement.ts +281 -0
- package/src/runtime/child-pi/child-pi-constants.ts +8 -0
- package/src/runtime/child-pi/child-pi-spawn.ts +60 -14
- package/src/runtime/child-pi/child-pi-streams.ts +21 -1
- package/src/runtime/child-pi/child-pi-timers.ts +324 -0
- package/src/runtime/child-pi/child-pi.ts +97 -201
- package/src/runtime/child-pi/mock-fixtures.ts +16 -2
- package/src/runtime/crew-agent-records.ts +259 -14
- package/src/runtime/delegate-spawn.ts +148 -0
- package/src/runtime/detached-run-results.ts +90 -0
- package/src/runtime/deterministic-ast.ts +2 -1
- package/src/runtime/dispatch-batch.ts +945 -0
- package/src/runtime/finalize-run.ts +557 -0
- package/src/runtime/goal-workflow/adaptive-plan.ts +116 -15
- package/src/runtime/goal-workflow/dynamic-workflow-runner.ts +8 -3
- package/src/runtime/goal-workflow/goal-state-store.ts +1 -1
- package/src/runtime/group-join.ts +11 -125
- package/src/runtime/live-session/live-session-runtime.ts +26 -1
- package/src/runtime/merge-gate.ts +32 -10
- package/src/runtime/merge-loop.ts +130 -0
- package/src/runtime/model/model-budget-summary.ts +53 -0
- package/src/runtime/model/model-fallback.ts +36 -2
- package/src/runtime/model/pi-args.ts +10 -0
- package/src/runtime/model/provider-extensions.ts +10 -0
- package/src/runtime/orphan-worker-registry.ts +1 -1
- package/src/runtime/output/output-validator.ts +45 -0
- package/src/runtime/parent-guard.ts +3 -1
- package/src/runtime/peer-dep.ts +2 -1
- package/src/runtime/per-write-validator.ts +0 -5
- package/src/runtime/pi-spawn.ts +61 -15
- package/src/runtime/plan-approval.ts +125 -0
- package/src/runtime/plan-replan.ts +151 -0
- package/src/runtime/process-status.ts +16 -1
- package/src/runtime/recovery/checkpoint.ts +0 -18
- package/src/runtime/recovery/crash-recovery.ts +111 -46
- package/src/runtime/run-tracker.ts +77 -10
- package/src/runtime/scheduler-context.ts +98 -0
- package/src/runtime/scheduling/coalesce-tasks.ts +5 -0
- package/src/runtime/scheduling/global-worker-cap.ts +2 -1
- package/src/runtime/scheduling/nested-slots.ts +70 -0
- package/src/runtime/scheduling/run-coalesced-task-group.ts +64 -13
- package/src/runtime/scheduling/task-graph-scheduler.ts +0 -10
- package/src/runtime/settings-store.ts +219 -0
- package/src/runtime/spawn-policy.ts +217 -0
- package/src/runtime/stale-reconciler.ts +87 -6
- package/src/runtime/subagent-manager.ts +25 -1
- package/src/runtime/task-output-context.ts +230 -9
- package/src/runtime/task-packet.ts +23 -1
- package/src/runtime/task-runner/child-executor.ts +106 -7
- package/src/runtime/task-runner/post-execution.ts +125 -1
- package/src/runtime/task-runner/pre-execution.ts +39 -1
- package/src/runtime/task-runner/prompt-builder.ts +51 -1
- package/src/runtime/task-runner/retrieval-orchestrator.ts +72 -18
- package/src/runtime/task-runner/spec-evidence.ts +403 -0
- package/src/runtime/task-runner/state-helpers.ts +26 -24
- package/src/runtime/task-runner.ts +11 -0
- package/src/runtime/team-runner.ts +132 -1673
- package/src/runtime/verification/spec-sandbox.ts +255 -0
- package/src/runtime/verification/verification-gates.ts +3 -2
- package/src/runtime/verification/verification-worktree.ts +2 -1
- package/src/runtime/workflow-phase-advance.ts +100 -0
- package/src/runtime/workspace-tree.ts +9 -0
- package/src/schema/config-schema.ts +66 -26
- package/src/schema/sensitive-config-paths.ts +64 -0
- package/src/schema/team-tool-schema.ts +13 -3
- package/src/state/README.md +4 -10
- package/src/state/atomic-write.ts +20 -3
- package/src/state/contracts.ts +38 -0
- package/src/state/coordination/mailbox.ts +12 -2
- package/src/state/event-log/cursor.ts +223 -0
- package/src/state/event-log/event-log-rotation.ts +12 -4
- package/src/state/event-log/event-log.ts +152 -369
- package/src/state/event-log/sequence-cache.ts +373 -0
- package/src/state/event-log/worker-atomic-writer.ts +2 -1
- package/src/state/stores/active-run-registry.ts +3 -2
- package/src/state/stores/manifest-io.ts +237 -0
- package/src/state/stores/ownership-map.ts +162 -0
- package/src/state/stores/plan-store.ts +241 -0
- package/src/state/stores/run-cache.ts +0 -90
- package/src/state/stores/spec-store.ts +189 -0
- package/src/state/stores/state-store.ts +139 -232
- package/src/state/types.ts +199 -0
- package/src/ui/dashboard-panes/plan-pane.ts +136 -0
- package/src/ui/dashboard-panes/progress-pane.ts +6 -0
- package/src/ui/dashboard-panes/transcript-pane.ts +31 -0
- package/src/ui/dock-footer.ts +49 -0
- package/src/ui/heartbeat-aggregator.ts +9 -1
- package/src/ui/inline-panel/agent-pane.ts +375 -0
- package/src/ui/inline-panel/agent-transcript.ts +338 -0
- package/src/ui/inline-panel/agent-view-overlay.ts +225 -0
- package/src/ui/inline-panel/crew-editor.ts +192 -0
- package/src/ui/inline-panel/index.ts +290 -0
- package/src/ui/inline-panel/panel-rows.ts +37 -0
- package/src/ui/inline-panel/panel-selection.ts +157 -0
- package/src/ui/inline-panel/panel-store.ts +111 -0
- package/src/ui/inline-panel/view-session-store.ts +36 -0
- package/src/ui/keybinding-map.ts +54 -13
- package/src/ui/pi-ui-compat.ts +9 -0
- package/src/ui/powerbar-publisher.ts +52 -1
- package/src/ui/run-dashboard.ts +31 -5
- package/src/ui/run-snapshot-cache.ts +57 -30
- package/src/ui/snapshot-types.ts +6 -1
- package/src/ui/widget/index.ts +176 -22
- package/src/ui/widget/task-list.ts +198 -0
- package/src/ui/widget/widget-formatters.ts +240 -4
- package/src/ui/widget/widget-renderer.ts +243 -38
- package/src/ui/widget/widget-types.ts +11 -0
- package/src/utils/child-process-shield.ts +106 -0
- package/src/utils/file-coalescer.ts +0 -4
- package/src/utils/fs-errno.ts +66 -0
- package/src/utils/fs-watch.ts +1 -1
- package/src/utils/internal-error.ts +3 -1
- package/src/utils/paths.ts +11 -3
- package/src/utils/redaction.ts +7 -0
- package/src/utils/safe-abort.ts +45 -0
- package/src/utils/task-name-generator.ts +1 -8
- package/src/workflows/discover-workflows.ts +20 -2
- package/src/workflows/validate-workflow.ts +7 -1
- package/src/workflows/workflow-config.ts +17 -0
- package/src/workflows/workflow-serializer.ts +3 -0
- package/src/worktree/worktree-manager.ts +22 -0
- package/workflows/default.workflow.md +36 -26
- package/workflows/strict-fast-fix.workflow.md +26 -0
- package/src/agents/agent-search.ts +0 -98
- package/src/benchmark/benchmark-runner.ts +0 -313
- package/src/benchmark/feedback-loop.ts +0 -73
- package/src/config/resilient-parser.ts +0 -117
- package/src/extension/crew-vibes/cat-frames.ts +0 -18
- package/src/extension/result-watcher.ts +0 -139
- package/src/observability/exporters/prometheus-exporter.ts +0 -54
- package/src/observability/metric-retention.ts +0 -64
- package/src/runtime/compaction/compaction-summary.ts +0 -278
- package/src/runtime/errors/crew-errors.ts +0 -162
- package/src/runtime/live-session/intercom-bridge.ts +0 -187
- package/src/runtime/loop-gates.ts +0 -128
- package/src/runtime/metric-parser.ts +0 -36
- package/src/runtime/output/stream-preview.ts +0 -184
- package/src/runtime/output/tool-progress.ts +0 -278
- package/src/runtime/phase-tracker.ts +0 -385
- package/src/runtime/pipeline-runner.ts +0 -523
- package/src/runtime/process/process-lifecycle.ts +0 -491
- package/src/runtime/recovery/retry-runner.ts +0 -330
- package/src/runtime/run-drift.ts +0 -219
- package/src/runtime/task-quality.ts +0 -199
- package/src/runtime/task-runner/run-projection.ts +0 -128
- package/src/runtime/verification/post-checks.ts +0 -142
- package/src/state/coordination/schedule.ts +0 -166
- package/src/state/event-log/jsonl-writer.ts +0 -115
- package/src/state/hook-instinct-bridge.ts +0 -94
- package/src/state/hook-integrations.ts +0 -51
- package/src/state/session-state-map.ts +0 -51
- package/src/state/stores/blob-store.ts +0 -308
- package/src/state/stores/instinct-store.ts +0 -275
- package/src/state/stores/observation-store.ts +0 -176
- package/src/state/tiered-eval.ts +0 -480
- package/src/state/types-eval.ts +0 -58
- package/src/tools/safe-bash-extension.ts +0 -54
- package/src/tools/safe-bash.ts +0 -505
- package/src/ui/agent-management-overlay.ts +0 -160
- package/src/ui/crew-footer.ts +0 -102
- package/src/ui/crew-select-list.ts +0 -114
- package/src/ui/dashboard-panes/capability-pane.ts +0 -77
- package/src/ui/transcript-entries.ts +0 -256
- package/src/utils/conflict-detect.ts +0 -721
- package/src/utils/fingerprint.ts +0 -180
- package/src/utils/gh-protocol.ts +0 -556
- package/src/utils/project-detector.ts +0 -160
- package/src/utils/sse-parser.ts +0 -131
- package/src/workflows/cost-estimator.ts +0 -34
- package/src/workflows/intermediate-store.ts +0 -166
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* worker-events-channel.ts — bounded worker self-reporting channel
|
|
3
|
+
* (WP-9 / R9, T5).
|
|
4
|
+
*
|
|
5
|
+
* The ONE worker-side primitive for appending self-reported, schema-tagged
|
|
6
|
+
* `worker.*` events to the run's events.jsonl (PI_CREW_EVENTS_PATH —
|
|
7
|
+
* unconditional since WP-9; previously scratchpad-gated, which excluded
|
|
8
|
+
* read-only roles by design and left them with no reporting path).
|
|
9
|
+
*
|
|
10
|
+
* Bounds (a worker must never be able to flood the shared event log):
|
|
11
|
+
* - SCHEMA: event types MUST match /^worker\.[a-z0-9_.-]{1,63}$/ — anything
|
|
12
|
+
* else is dropped (return false). Worker events never impersonate
|
|
13
|
+
* orchestrator namespaces (task.*, run.*, spec.*, …).
|
|
14
|
+
* - RATE LIMIT: sliding window, `maxEventsPerWindow` (default 30) per
|
|
15
|
+
* `windowMs` (default 60s). Dropped events are counted; the next ACCEPTED
|
|
16
|
+
* event carries `droppedSinceLast` so the operator sees suppression.
|
|
17
|
+
* - FIFO BUFFER CAP: failed appends (throwing writer, e.g. transient EBUSY)
|
|
18
|
+
* queue for retry; the queue holds at most `bufferCap` (default 64) —
|
|
19
|
+
* overflow drops the OLDEST pending (FIFO), never grows unboundedly.
|
|
20
|
+
* - CRASH SAFETY: each append is a single O_APPEND write of one JSONL line.
|
|
21
|
+
* A crash mid-write leaves at most a trailing PARTIAL line — the
|
|
22
|
+
* orchestrator-side reader (parseJsonlEvents) already skips corrupt lines,
|
|
23
|
+
* so complete events before/after the partial stay readable (recoverable).
|
|
24
|
+
*
|
|
25
|
+
* Heartbeats remain the liveness corroboration source; worker.* events are
|
|
26
|
+
* informational only — the scheduler never derives liveness from them.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { appendFileSync, readFileSync } from "node:fs";
|
|
30
|
+
|
|
31
|
+
export const WORKER_EVENT_TYPE_PATTERN = /^worker\.[a-z0-9_.-]{1,63}$/;
|
|
32
|
+
|
|
33
|
+
export interface WorkerEventsChannelOptions {
|
|
34
|
+
/** Default: process.env (worker context). */
|
|
35
|
+
env?: NodeJS.ProcessEnv;
|
|
36
|
+
/** Append function — default: appendEventFireAndForget from state. */
|
|
37
|
+
appendEvent?: (eventsPath: string, event: Record<string, unknown>) => void;
|
|
38
|
+
now?: () => number;
|
|
39
|
+
/** Sliding-window rate limit: max accepted events per windowMs. */
|
|
40
|
+
maxEventsPerWindow?: number;
|
|
41
|
+
windowMs?: number;
|
|
42
|
+
/** FIFO cap for queued (failed) appends. */
|
|
43
|
+
bufferCap?: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface WorkerEventsChannel {
|
|
47
|
+
/** Emit a worker.* event. false = dropped (schema/rate/buffer reasons). */
|
|
48
|
+
emit(type: string, data: Record<string, unknown>): boolean;
|
|
49
|
+
/** Retry queued appends (called on the next emit internally). */
|
|
50
|
+
flush(): void;
|
|
51
|
+
/** Test introspection. */
|
|
52
|
+
stats(): { accepted: number; droppedSchema: number; droppedRate: number; droppedBuffer: number; queued: number };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function createWorkerEventsChannel(options: WorkerEventsChannelOptions = {}): WorkerEventsChannel {
|
|
56
|
+
const env = options.env ?? process.env;
|
|
57
|
+
const now = options.now ?? Date.now;
|
|
58
|
+
const appendEvent =
|
|
59
|
+
options.appendEvent ??
|
|
60
|
+
((eventsPath: string, event: Record<string, unknown>) => {
|
|
61
|
+
// Single O_APPEND write of one JSONL line — the crash-safety contract.
|
|
62
|
+
// A previous crash may have left a trailing PARTIAL line (no newline):
|
|
63
|
+
// appending without a separator would fuse both lines into ONE corrupt
|
|
64
|
+
// line, destroying the complete event too. Guard: if the file is
|
|
65
|
+
// non-empty and does not end with \n, prepend the separator (1-byte
|
|
66
|
+
// read of the tail — the partial line stays quarantined on its own line,
|
|
67
|
+
// which the orchestrator reader skips).
|
|
68
|
+
let prefix = "";
|
|
69
|
+
try {
|
|
70
|
+
const buf = readFileSync(eventsPath);
|
|
71
|
+
if (buf.length > 0 && buf[buf.length - 1] !== 0x0a) prefix = "\n";
|
|
72
|
+
} catch {
|
|
73
|
+
/* absent file — nothing to separate */
|
|
74
|
+
}
|
|
75
|
+
appendFileSync(eventsPath, `${prefix}${JSON.stringify(event)}\n`, "utf-8");
|
|
76
|
+
});
|
|
77
|
+
const maxPerWindow = options.maxEventsPerWindow ?? 30;
|
|
78
|
+
const windowMs = options.windowMs ?? 60_000;
|
|
79
|
+
const bufferCap = options.bufferCap ?? 64;
|
|
80
|
+
|
|
81
|
+
const eventsPath = env.PI_CREW_EVENTS_PATH;
|
|
82
|
+
const runId = env.PI_CREW_BROKER_RUN_ID;
|
|
83
|
+
const taskId = env.PI_CREW_TASK_ID;
|
|
84
|
+
|
|
85
|
+
const windowStarts: number[] = [];
|
|
86
|
+
const pending: { type: string; data: Record<string, unknown>; droppedSinceLast: number }[] = [];
|
|
87
|
+
const counters = { accepted: 0, droppedSchema: 0, droppedRate: 0, droppedBuffer: 0 };
|
|
88
|
+
// Dropped-due-to-rate counter surfaced on the next ACCEPTED event (the
|
|
89
|
+
// operator sees suppression without a flood of drop events).
|
|
90
|
+
let suppressedSinceLast = 0;
|
|
91
|
+
|
|
92
|
+
const write = (item: { type: string; data: Record<string, unknown>; droppedSinceLast: number }): boolean => {
|
|
93
|
+
try {
|
|
94
|
+
appendEvent(eventsPath as string, {
|
|
95
|
+
type: item.type,
|
|
96
|
+
runId,
|
|
97
|
+
taskId,
|
|
98
|
+
data: item.droppedSinceLast > 0 ? { ...item.data, droppedSinceLast: item.droppedSinceLast } : item.data,
|
|
99
|
+
});
|
|
100
|
+
return true;
|
|
101
|
+
} catch {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const flushInternal = (currentDropped: number): number => {
|
|
107
|
+
let droppedCarry = currentDropped;
|
|
108
|
+
while (pending.length > 0) {
|
|
109
|
+
const item = pending[0]!;
|
|
110
|
+
if (write(item)) {
|
|
111
|
+
pending.shift();
|
|
112
|
+
counters.accepted++;
|
|
113
|
+
droppedCarry = 0;
|
|
114
|
+
} else {
|
|
115
|
+
// Writer still failing — keep the queue (bounded), stop here.
|
|
116
|
+
return droppedCarry;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return droppedCarry;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
emit(type: string, data: Record<string, unknown>): boolean {
|
|
124
|
+
if (!eventsPath || !runId) return false; // non-team context — no-op
|
|
125
|
+
if (!WORKER_EVENT_TYPE_PATTERN.test(type)) {
|
|
126
|
+
counters.droppedSchema++;
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
// Sliding-window rate limit.
|
|
130
|
+
const t = now();
|
|
131
|
+
while (windowStarts.length > 0 && t - windowStarts[0]! >= windowMs) windowStarts.shift();
|
|
132
|
+
if (windowStarts.length >= maxPerWindow) {
|
|
133
|
+
counters.droppedRate++;
|
|
134
|
+
suppressedSinceLast++;
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
windowStarts.push(t);
|
|
138
|
+
// Drain any queued items first (FIFO — oldest first), then the new one.
|
|
139
|
+
// The new event carries the suppression count (queue drops are already
|
|
140
|
+
// counted on their own accepted re-write).
|
|
141
|
+
if (pending.length > 0) {
|
|
142
|
+
const carry = flushInternal(0);
|
|
143
|
+
if (carry > 0) {
|
|
144
|
+
// Writer failing: queue the new item under the FIFO cap.
|
|
145
|
+
if (pending.length >= bufferCap) {
|
|
146
|
+
pending.shift();
|
|
147
|
+
counters.droppedBuffer++;
|
|
148
|
+
}
|
|
149
|
+
pending.push({ type, data, droppedSinceLast: 0 });
|
|
150
|
+
return true; // accepted (queued)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (write({ type, data, droppedSinceLast: suppressedSinceLast })) {
|
|
154
|
+
counters.accepted++;
|
|
155
|
+
suppressedSinceLast = 0;
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
if (pending.length >= bufferCap) {
|
|
159
|
+
pending.shift();
|
|
160
|
+
counters.droppedBuffer++;
|
|
161
|
+
}
|
|
162
|
+
pending.push({ type, data, droppedSinceLast: suppressedSinceLast });
|
|
163
|
+
suppressedSinceLast = 0;
|
|
164
|
+
return true;
|
|
165
|
+
},
|
|
166
|
+
flush(): void {
|
|
167
|
+
flushInternal(0);
|
|
168
|
+
},
|
|
169
|
+
stats(): { accepted: number; droppedSchema: number; droppedRate: number; droppedBuffer: number; queued: number } {
|
|
170
|
+
return { ...counters, queued: pending.length };
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
}
|
package/src/runtime/README.md
CHANGED
|
@@ -10,26 +10,26 @@ clusters are extracted into subdirectories; the remaining files stay at the root
|
|
|
10
10
|
| [`child-pi/`](./child-pi/) | B-1 | child Pi worker spawn/lifecycle/steering/transcript | `child-pi.ts`, `child-pi-spawn.ts`, `child-pi-kill.ts`, `child-pi-constants.ts`, `child-pi-steering.ts`, `child-pi-streams.ts`, `child-pi-transcript.ts` |
|
|
11
11
|
| [`broker/`](./broker/) | B-2 | crew broker server/client/auth (child-pi ↔ parent IPC) | `crew-broker.ts`, `crew-broker-client.ts`, `crew-broker-child.ts`, `crew-broker-tokens.ts`, `broker-issuer.ts` |
|
|
12
12
|
| [`task-runner/`](./task-runner/) | (pre-existing) | per-task execution (pre-execution, child-executor) | `child-executor.ts`, `pre-execution.ts`, ... |
|
|
13
|
-
| [`live-session/`](./live-session/) | 3 | live agent control/manager, session runtime, IRC, health, extension bridge | `live-session-runtime.ts`, `live-agent-manager.ts`, `live-agent-control.ts`, `live-control-realtime.ts`, `live-irc.ts`, `live-session-health.ts`, `live-extension-bridge.ts
|
|
14
|
-
| [`recovery/`](./recovery/) | 4 | crash recovery, retry, checkpoint | `crash-recovery.ts`, `crash-classification.ts`, `recovery-recipes.ts`, `overflow-recovery.ts`, `retry-executor.ts`, `
|
|
13
|
+
| [`live-session/`](./live-session/) | 3 | live agent control/manager, session runtime, IRC, health, extension bridge | `live-session-runtime.ts`, `live-agent-manager.ts`, `live-agent-control.ts`, `live-control-realtime.ts`, `live-irc.ts`, `live-session-health.ts`, `live-extension-bridge.ts` |
|
|
14
|
+
| [`recovery/`](./recovery/) | 4 | crash recovery, retry, checkpoint | `crash-recovery.ts`, `crash-classification.ts`, `recovery-recipes.ts`, `overflow-recovery.ts`, `retry-executor.ts`, `checkpoint.ts` |
|
|
15
15
|
| [`scheduling/`](./scheduling/) | 5 | scheduler, concurrency, task-graph, batch-barrier, parallel utils | `scheduler.ts`, `semaphore.ts`, `concurrency.ts`, `task-graph.ts`, `task-graph-scheduler.ts`, `batch-barrier.ts`, `coalesce-tasks.ts`, `run-coalesced-task-group.ts`, `global-worker-cap.ts`, `parallel-research.ts`, `parallel-utils.ts` |
|
|
16
|
-
| [`verification/`](./verification/) | 6a | verification gates, green-contract,
|
|
16
|
+
| [`verification/`](./verification/) | 6a | verification gates, green-contract, completion-guard | `verification-gates.ts`, `verification-integrity.ts`, `verification-worktree.ts`, `green-contract.ts`, `completion-guard.ts` |
|
|
17
17
|
| [`model/`](./model/) | 6b | model fallback/resolver/scope, pi-args, runtime policy/resolver/warmup | `model-fallback.ts`, `model-resolver.ts`, `model-scope.ts`, `pi-args.ts`, `runtime-policy.ts`, `runtime-resolver.ts`, `runtime-warmup.ts` |
|
|
18
|
-
| [`output/`](./output/) | 6c | streaming output, progress tracking, sidechain, result extraction | `streaming-output.ts`, `
|
|
18
|
+
| [`output/`](./output/) | 6c | streaming output, progress tracking, sidechain, result extraction | `streaming-output.ts`, `output-validator.ts`, `pi-json-output.ts`, `progress-event-coalescer.ts`, `progress-tracker.ts`, `result-extractor.ts`, `sidechain-output.ts` |
|
|
19
19
|
| [`heartbeat/`](./heartbeat/) | 6d | heartbeat gradient/watcher, worker heartbeat/startup | `heartbeat-gradient.ts`, `heartbeat-watcher.ts`, `worker-heartbeat.ts`, `worker-startup.ts` |
|
|
20
|
-
| [`process/`](./process/) | 6e | cancellation,
|
|
20
|
+
| [`process/`](./process/) | 6e | cancellation, zombie scanner, stdio guard | `cancellation-token.ts`, `cancellation.ts`, `zombie-scanner.ts`, `post-exit-stdio-guard.ts` |
|
|
21
21
|
| [`goal-workflow/`](./goal-workflow/) | 6f | goal achievement/evaluation/loop, dynamic-workflow, adaptive plan | `goal-achievement.ts`, `goal-evaluator.ts`, `goal-loop-runner.ts`, `goal-state-store.ts`, `dynamic-workflow-context.ts`, `dynamic-workflow-runner.ts`, `adaptive-plan.ts`, `plan-templates.ts` |
|
|
22
|
-
| [`compaction/`](./compaction/) | 6g | compaction pipeline, tool-output-pruner, important-line-classifier, compact-stages | `compact-pipeline.ts`, `
|
|
22
|
+
| [`compaction/`](./compaction/) | 6g | compaction pipeline, tool-output-pruner, important-line-classifier, compact-stages | `compact-pipeline.ts`, `tool-output-pruner.ts`, `important-line-classifier.ts`, [`compact-stages/`](./compaction/compact-stages/) |
|
|
23
23
|
| [`custom-tools/`](./custom-tools/) | (pre-existing) | custom tool definitions | `irc-tool.ts`, `submit-result-tool.ts`, ... |
|
|
24
24
|
| [`errors/`](./errors/) | (pre-existing) | error types | ... |
|
|
25
25
|
|
|
26
26
|
## Remaining flat files at root (~77)
|
|
27
27
|
|
|
28
28
|
- **team-runner** — `team-runner.ts` (the orchestrator) + `run-worker.ts`, `run-tracker.ts`
|
|
29
|
-
- **chain
|
|
29
|
+
- **chain runners** — `chain-parser.ts`, `chain-runner.ts`, `direct-run.ts`
|
|
30
30
|
- **foreground/background runners** — `foreground-control.ts`, `foreground-watchdog.ts`, `async-runner.ts`, `background-runner.ts`
|
|
31
31
|
- **crew agent** — `crew-agent-records.ts`, `crew-agent-runtime.ts`, `crew-hooks.ts`, `single-agent-compose.ts`
|
|
32
|
-
- **observability** — `effectiveness.ts`, `agent-observability.ts`, `command-trace.ts
|
|
32
|
+
- **observability** — `effectiveness.ts`, `agent-observability.ts`, `command-trace.ts`
|
|
33
33
|
- **misc** — `stale-reconciler.ts`, `parent-guard.ts`, `manifest-cache.ts`, `event-stream-bridge.ts`, `policy-engine.ts`, `role-permission.ts`, `workspace-lock.ts`, `workspace-tree.ts`, `session-*.ts`, ...
|
|
34
34
|
|
|
35
35
|
See `docs/COVERAGE-ASSESSMENT-2026-08-01.md` § "Source reorganization" for the reorg history + remaining-cluster plan.
|
|
@@ -3,6 +3,7 @@ import * as fs from "node:fs";
|
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
|
+
import { getCrewEnv } from "../config/env-vars.ts";
|
|
6
7
|
import { appendEventAsync } from "../state/event-log/event-log.ts";
|
|
7
8
|
import type { TeamRunManifest } from "../state/types.ts";
|
|
8
9
|
import { WINDOWS_ESSENTIAL_ENV_VARS } from "../utils/env-allowlist.ts";
|
|
@@ -54,8 +55,11 @@ export function resolveJitiRegisterPath(packageRoot = packageRootFromRuntime(),
|
|
|
54
55
|
path.join(path.dirname(pkgPath), "dist", "register.mjs"),
|
|
55
56
|
];
|
|
56
57
|
for (const c of candidates) if (exists(c)) return c;
|
|
57
|
-
} catch {
|
|
58
|
-
//
|
|
58
|
+
} catch (error) {
|
|
59
|
+
// R17-B4 (LOW): fall-through is benign (undefined → next loader
|
|
60
|
+
// strategy), but keep the require.resolve root cause visible under
|
|
61
|
+
// PI_TEAMS_DEBUG instead of a bare catch.
|
|
62
|
+
logInternalError("async-runner.jiti-resolve", error, "require.resolve('jiti/package.json') fallback failed", "debug");
|
|
59
63
|
}
|
|
60
64
|
return undefined;
|
|
61
65
|
}
|
|
@@ -124,7 +128,7 @@ export function getBackgroundRunnerCommand(
|
|
|
124
128
|
// default precisely so silent runner deaths (like the explore→code-review
|
|
125
129
|
// transition crash) leave a native stack/heap/environment trace. Users who
|
|
126
130
|
// don't want report files can opt out with PI_CREW_BG_REPORT_ON_FATAL=0.
|
|
127
|
-
const reportOn = !(
|
|
131
|
+
const reportOn = !(getCrewEnv("PI_CREW_BG_REPORT_ON_FATAL") === "0" || getCrewEnv("PI_TEAMS_BG_REPORT_ON_FATAL") === "0");
|
|
128
132
|
const reportDir = reportDirectory ?? path.dirname(runnerPath);
|
|
129
133
|
const reportFlags = reportOn ? ["--report-on-fatalerror", "--report-compact", `--report-directory=${reportDir}`] : [];
|
|
130
134
|
if (loader.kind === "jiti") {
|
|
@@ -2,6 +2,7 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { allAgents, discoverAgents } from "../agents/discover-agents.ts";
|
|
4
4
|
import { loadConfig } from "../config/config.ts";
|
|
5
|
+
import { getCrewEnv } from "../config/env-vars.ts";
|
|
5
6
|
import { atomicWriteFile } from "../state/atomic-write.ts";
|
|
6
7
|
import { withRunLockSync } from "../state/coordination/locks.ts";
|
|
7
8
|
import { appendEvent, appendEventFireAndForget } from "../state/event-log/event-log.ts";
|
|
@@ -10,6 +11,7 @@ import type { TeamRunManifest, TeamTaskState } from "../state/types.ts";
|
|
|
10
11
|
import { allTeams, discoverTeams } from "../teams/discover-teams.ts";
|
|
11
12
|
import { errorMessage } from "../utils/guards.ts";
|
|
12
13
|
import { projectCrewRoot } from "../utils/paths.ts";
|
|
14
|
+
import { assertSafePathId } from "../utils/safe-paths.ts";
|
|
13
15
|
import { allWorkflows, discoverWorkflows } from "../workflows/discover-workflows.ts";
|
|
14
16
|
// Heavy runtime — lazy-loaded to avoid pulling team-runner into background-runner
|
|
15
17
|
// at module load time. Only needed when a background run actually starts.
|
|
@@ -27,7 +29,7 @@ let _cachedExecuteTeamRun: typeof ExecuteTeamRunFn | undefined;
|
|
|
27
29
|
* aborts via the shared AbortController, then force-exits after a grace
|
|
28
30
|
* period in case the abort signal does not propagate to all execution paths. */
|
|
29
31
|
const MAX_BACKGROUND_RUN_MS = (() => {
|
|
30
|
-
const env = Number.parseInt(
|
|
32
|
+
const env = Number.parseInt(getCrewEnv("PI_CREW_MAX_RUN_MS") ?? "", 10);
|
|
31
33
|
return Number.isFinite(env) && env > 0 ? env : 2 * 60 * 60 * 1000;
|
|
32
34
|
})();
|
|
33
35
|
async function executeTeamRun(...args: Parameters<typeof ExecuteTeamRunFn>): Promise<Awaited<ReturnType<typeof ExecuteTeamRunFn>>> {
|
|
@@ -59,7 +61,7 @@ import { expandParallelResearchWorkflow } from "./scheduling/parallel-research.t
|
|
|
59
61
|
* while keeping diagnostics available when explicitly enabled.
|
|
60
62
|
*/
|
|
61
63
|
function debugLog(message: string): void {
|
|
62
|
-
if (
|
|
64
|
+
if (getCrewEnv("PI_CREW_DEBUG")) console.log(message);
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
/**
|
|
@@ -213,7 +215,7 @@ export function startInterruptGuard(
|
|
|
213
215
|
const controlPath = path.join(manifest.stateRoot, "foreground-control.json");
|
|
214
216
|
// FIX: Made configurable via PI_CREW_INTERRUPT_GUARD_INTERVAL_MS env var.
|
|
215
217
|
// Default 250ms balances fast SIGINT response against filesystem overhead.
|
|
216
|
-
const interruptGuardInterval = Number(
|
|
218
|
+
const interruptGuardInterval = Number(getCrewEnv("PI_CREW_INTERRUPT_GUARD_INTERVAL_MS")) || 250;
|
|
217
219
|
// RT-4 FIX: Module-local gate so the interrupt body runs only once per
|
|
218
220
|
// interrupt request. Without this, the guard re-fires every
|
|
219
221
|
// interruptGuardInterval (250ms) — each tick does a full
|
|
@@ -410,6 +412,13 @@ async function main(): Promise<void> {
|
|
|
410
412
|
const _cwd = argValue("--cwd");
|
|
411
413
|
const _runId = argValue("--run-id");
|
|
412
414
|
if (_cwd && _runId) {
|
|
415
|
+
// R11-2 (LOW, §ROUND 11 security hardening): assert the argv-supplied
|
|
416
|
+
// runId at the argValue boundary BEFORE any path construction — the
|
|
417
|
+
// background.log join would otherwise embed an untrusted runId (future
|
|
418
|
+
// user-supplied --resume) → path traversal outside the run root. Throws
|
|
419
|
+
// at startup for unsafe ids (intended fail-fast; placed OUTSIDE the
|
|
420
|
+
// best-effort try/catch so the hardening is never silently swallowed).
|
|
421
|
+
assertSafePathId("runId", _runId);
|
|
413
422
|
try {
|
|
414
423
|
// Use projectCrewRoot() so the background log lives next to the
|
|
415
424
|
// manifest in either .crew/state/runs/ or .pi/teams/state/runs/
|
|
@@ -464,6 +473,10 @@ async function main(): Promise<void> {
|
|
|
464
473
|
const cwd = argValue("--cwd");
|
|
465
474
|
const runId = argValue("--run-id");
|
|
466
475
|
if (!cwd || !runId) return undefined;
|
|
476
|
+
// R11-2 (LOW, §ROUND 11): same argv boundary hardening as main() — this
|
|
477
|
+
// IIFE runs at MODULE LOAD, so an unsafe runId throws before the runner
|
|
478
|
+
// starts (intended fail-fast, matching run-import.ts:105 pattern).
|
|
479
|
+
assertSafePathId("runId", runId);
|
|
467
480
|
// Use projectCrewRoot() to honour the .pi/teams/ fallback (issue #29).
|
|
468
481
|
return path.join(projectCrewRoot(cwd), "state", "runs", runId, "exit-code.txt");
|
|
469
482
|
})();
|
|
@@ -611,7 +624,7 @@ async function main(): Promise<void> {
|
|
|
611
624
|
setupUnhandledRejectionGuard(rejectionGuardState, abortController, setExitFlag);
|
|
612
625
|
|
|
613
626
|
// Start parent guard — if parent is already dead, exit immediately
|
|
614
|
-
const parentPid = Number(
|
|
627
|
+
const parentPid = Number(getCrewEnv("PI_CREW_PARENT_PID"));
|
|
615
628
|
if (parentPid > 0) startParentGuard(parentPid);
|
|
616
629
|
// NOTE: intentionally no unref() — the guard keeps the event loop alive
|
|
617
630
|
// to prevent premature worker exit. See parent-guard.ts:86 for rationale.
|
|
@@ -880,10 +893,32 @@ async function main(): Promise<void> {
|
|
|
880
893
|
if (manifest.status === "failed" || manifest.status === "cancelled" || manifest.status === "blocked") process.exitCode = 1;
|
|
881
894
|
} catch (error) {
|
|
882
895
|
// Terminate live agents on failure too — agents are done when the run fails
|
|
896
|
+
const message = errorMessage(error);
|
|
883
897
|
try {
|
|
884
|
-
|
|
885
|
-
//
|
|
886
|
-
|
|
898
|
+
// R14-2: re-read INSIDE the lock and derive the failed-status write from the
|
|
899
|
+
// FRESH manifest. The outer `manifest` is a pre-throw snapshot — using it
|
|
900
|
+
// here could flip a concurrently completed/cancelled run back to "failed"
|
|
901
|
+
// (updateRunStatus validates against the fresh on-disk status; an illegal
|
|
902
|
+
// flip throws and is swallowed by the best-effort catch below). Keeping the
|
|
903
|
+
// write inside withRunLockSync prevents race with concurrent writers
|
|
904
|
+
// (e.g., stale reconciler) between the read and the subsequent save.
|
|
905
|
+
const manifestToUse = withRunLockSync(
|
|
906
|
+
manifest,
|
|
907
|
+
() => {
|
|
908
|
+
const loaded = loadRunManifestById(cwd, runId);
|
|
909
|
+
const fresh = loaded?.manifest ?? manifest;
|
|
910
|
+
if (fresh) {
|
|
911
|
+
manifest = updateRunStatus(fresh, "failed", message);
|
|
912
|
+
appendEvent(manifest.eventsPath, {
|
|
913
|
+
type: "async.failed",
|
|
914
|
+
runId: manifest.runId,
|
|
915
|
+
message,
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
return fresh;
|
|
919
|
+
},
|
|
920
|
+
{ staleMs: 30_000 },
|
|
921
|
+
);
|
|
887
922
|
if (manifestToUse) {
|
|
888
923
|
// LAZY: live-agent-manager only needed on failure cleanup path; avoid module load at hot path.
|
|
889
924
|
const { terminateLiveAgentsForRun } = await import("./live-session/live-agent-manager.ts");
|
|
@@ -894,13 +929,6 @@ async function main(): Promise<void> {
|
|
|
894
929
|
} catch {
|
|
895
930
|
/* best-effort */
|
|
896
931
|
}
|
|
897
|
-
const message = errorMessage(error);
|
|
898
|
-
manifest = updateRunStatus(manifest, "failed", message);
|
|
899
|
-
appendEvent(manifest.eventsPath, {
|
|
900
|
-
type: "async.failed",
|
|
901
|
-
runId: manifest.runId,
|
|
902
|
-
message,
|
|
903
|
-
});
|
|
904
932
|
process.exitCode = 1;
|
|
905
933
|
console.log(`[background-runner] catch block, error=${errorMessage(error)}`);
|
|
906
934
|
} finally {
|
|
@@ -23,8 +23,15 @@ export interface BrokerSpawnCredentials {
|
|
|
23
23
|
/** Issuer signature: given a runId (+optional taskId for per-task tokens),
|
|
24
24
|
* return credentials or undefined when the broker is disabled / this process
|
|
25
25
|
* is not the root session. taskId is optional for backward compat — callers
|
|
26
|
-
* that still pass only runId receive the legacy per-run token.
|
|
27
|
-
|
|
26
|
+
* that still pass only runId receive the legacy per-run token.
|
|
27
|
+
*
|
|
28
|
+
* ADR-5 §4 (governed nesting): `childDepth` carries the DEPTH-2+ grandchild's
|
|
29
|
+
* depth when the root-side delegate handler spawns it. The issuer mints only
|
|
30
|
+
* for children that may themselves delegate (childDepth < resolved
|
|
31
|
+
* PI_CREW_MAX_DEPTH) — at the default maxDepth=2 a depth-2 grandchild gets NO
|
|
32
|
+
* credentials (env containment AC: no PI_CREW_BROKER_SOCKET/TOKEN at depth 2).
|
|
33
|
+
* Undefined childDepth = legacy worker spawn (depth 1) — unchanged behavior. */
|
|
34
|
+
export type BrokerIssuer = (runId: string, taskId?: string, childDepth?: number) => Promise<BrokerSpawnCredentials | undefined>;
|
|
28
35
|
|
|
29
36
|
let activeIssuer: BrokerIssuer | undefined;
|
|
30
37
|
|
|
@@ -123,17 +123,54 @@ export class BrokerTokenRegistry {
|
|
|
123
123
|
* if the candidate matches the run's orchestrator token, "worker" if it
|
|
124
124
|
* matches a per-task/per-run token, or null if neither. Orchestrator key
|
|
125
125
|
* is checked FIRST so an orchestrator token can never be confused with a
|
|
126
|
-
* task token (the two are cryptographically distinct by construction).
|
|
126
|
+
* task token (the two are cryptographically distinct by construction).
|
|
127
|
+
* Semantics unchanged since extraction; now delegates to
|
|
128
|
+
* tokenRoleWithMatchKind() so both share one compare path. */
|
|
127
129
|
tokenRole(runId: string, taskId: string | undefined, candidate: unknown): "orchestrator" | "worker" | null {
|
|
130
|
+
const resolved = this.tokenRoleWithMatchKind(runId, taskId, candidate);
|
|
131
|
+
return resolved === null ? null : resolved.role;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Same resolution as tokenRole() but also reports HOW the token matched:
|
|
135
|
+
* `"compound"` (task-scoped `${runId}:${taskId}` key — or the
|
|
136
|
+
* orchestrator key, which is runId-prefixed by construction and can
|
|
137
|
+
* never be reached via the fallback) vs `"runId-fallback"` (legacy
|
|
138
|
+
* bare-runId key matched because no compound key was issued).
|
|
139
|
+
*
|
|
140
|
+
* WP-2/R2 (ADR-0 2026-08-17-waiting-producer-ask item 6): the broker's
|
|
141
|
+
* `wait.*` methods authenticate via task-scoped tokens ONLY — a
|
|
142
|
+
* `"runId-fallback"` match is rejected at the dispatch site with a
|
|
143
|
+
* migrate hint. Legacy methods keep using tokenRole()/matches() and
|
|
144
|
+
* therefore keep accepting the fallback (backward compat preserved).
|
|
145
|
+
*
|
|
146
|
+
* Match semantics mirror tokenRole() exactly: the bare-runId fallback is
|
|
147
|
+
* consulted ONLY when no compound key exists for (runId, taskId) — a
|
|
148
|
+
* candidate matching the bare key while a DIFFERENT compound key exists
|
|
149
|
+
* still returns null (no accidental fallback activation). */
|
|
150
|
+
tokenRoleWithMatchKind(
|
|
151
|
+
runId: string,
|
|
152
|
+
taskId: string | undefined,
|
|
153
|
+
candidate: unknown,
|
|
154
|
+
): { role: "orchestrator" | "worker"; matchKind: "compound" | "runId-fallback" } | null {
|
|
128
155
|
if (BrokerTokenRegistry.equalConstTime(this.map.get(this.orchestratorKey(runId)), candidate)) {
|
|
129
|
-
return "orchestrator";
|
|
156
|
+
return { role: "orchestrator", matchKind: "compound" };
|
|
157
|
+
}
|
|
158
|
+
// No taskId → the key degenerates to bare runId; there is no task
|
|
159
|
+
// scoping at all, so an honest matchKind is "runId-fallback".
|
|
160
|
+
if (taskId === undefined) {
|
|
161
|
+
return BrokerTokenRegistry.equalConstTime(this.map.get(runId), candidate)
|
|
162
|
+
? { role: "worker", matchKind: "runId-fallback" }
|
|
163
|
+
: null;
|
|
164
|
+
}
|
|
165
|
+
const compound = this.map.get(this.key(runId, taskId));
|
|
166
|
+
if (compound !== undefined) {
|
|
167
|
+
return BrokerTokenRegistry.equalConstTime(compound, candidate) ? { role: "worker", matchKind: "compound" } : null;
|
|
130
168
|
}
|
|
131
|
-
let expected = this.map.get(this.key(runId, taskId));
|
|
132
169
|
// Backward-compat: per-run token issued without taskId.
|
|
133
|
-
if (
|
|
134
|
-
|
|
170
|
+
if (BrokerTokenRegistry.equalConstTime(this.map.get(runId), candidate)) {
|
|
171
|
+
return { role: "worker", matchKind: "runId-fallback" };
|
|
135
172
|
}
|
|
136
|
-
return
|
|
173
|
+
return null;
|
|
137
174
|
}
|
|
138
175
|
|
|
139
176
|
/** Remove the token for `runId` (+optional `taskId`).
|