pi-subagents 0.65.1 → 0.67.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/CHANGELOG.md +123 -0
- package/README.md +5 -4
- package/agents/evidence-auditor.md +34 -0
- package/agents/researcher.md +23 -13
- package/agents/reviewer.md +3 -2
- package/docs/agents.md +20 -3
- package/docs/configuration.md +25 -5
- package/docs/extension-api.md +124 -18
- package/docs/missions.md +8 -0
- package/docs/models.md +59 -2
- package/docs/observability.md +46 -6
- package/docs/standalone-background.md +49 -0
- package/docs/tool-reference.md +20 -10
- package/docs/watchdog.md +35 -4
- package/docs/workflows.md +40 -19
- package/inspector-runner.mjs +2 -2
- package/package.json +2 -1
- package/prompts/parallel-review.md +1 -1
- package/{runner-server-preload.mjs → runner-peer-preload.mjs} +8 -3
- package/skills/pi-subagents/SKILL.md +14 -0
- package/skills/pi-subagents/references/execution-controls.md +20 -5
- package/skills/pi-subagents/references/management-authoring-rpc.md +2 -1
- package/skills/pi-subagents/references/prompting-and-roles.md +2 -2
- package/src/agents/advertised-agent-prompt.ts +94 -0
- package/src/agents/agent-management.ts +14 -1
- package/src/agents/agent-serializer.ts +2 -0
- package/src/agents/agents.ts +14 -0
- package/src/agents/builtin-names.ts +1 -0
- package/src/api/delegation.ts +4 -0
- package/src/api/preflight.ts +76 -45
- package/src/api/shared-types.ts +3 -1
- package/src/api/workflow-resources.ts +6 -0
- package/src/extension/fanout-child.ts +63 -4
- package/src/extension/index.ts +58 -8
- package/src/extension/public-execution.ts +4 -3
- package/src/extension/rpc.ts +8 -21
- package/src/extension/schemas.ts +71 -80
- package/src/extension/tool-description.ts +29 -81
- package/src/inspectors/actions.ts +148 -0
- package/src/inspectors/ghostty/actions.ts +74 -0
- package/src/inspectors/ghostty/plugin.ts +17 -0
- package/src/inspectors/herdr/actions.ts +99 -179
- package/src/inspectors/herdr/plugin.ts +20 -0
- package/src/inspectors/herdr/project-panes.ts +1 -1
- package/src/inspectors/{herdr/inspector-runner.ts → inspector-runner.ts} +12 -12
- package/src/inspectors/plugins.ts +8 -0
- package/src/inspectors/{herdr/session-roots-codec.ts → session-roots-codec.ts} +3 -14
- package/src/inspectors/types.ts +51 -0
- package/src/intercom/intercom-bridge.ts +50 -8
- package/src/intercom/native-supervisor-channel.ts +104 -67
- package/src/runs/background/active-async-capacity.ts +22 -18
- package/src/runs/background/async-execution.ts +45 -56
- package/src/runs/background/async-job-tracker.ts +35 -3
- package/src/runs/background/async-resume.ts +5 -9
- package/src/runs/background/async-status-snapshot.ts +10 -12
- package/src/runs/background/async-status.ts +17 -9
- package/src/runs/background/auto-drain.ts +44 -30
- package/src/runs/background/binary-bootstrap.ts +33 -0
- package/src/runs/background/chain-root-attachment.ts +8 -0
- package/src/runs/background/control-channel.ts +78 -44
- package/src/runs/background/fleet-view.ts +30 -2
- package/src/runs/background/notify.ts +117 -13
- package/src/runs/background/owned-process-tree.ts +35 -8
- package/src/runs/background/process-terminal.ts +23 -23
- package/src/runs/background/run-child-session.ts +121 -36
- package/src/runs/background/run-status.ts +78 -5
- package/src/runs/background/runner-aliases.ts +28 -9
- package/src/runs/background/runner-child-launch.ts +88 -0
- package/src/runs/background/runner-child-sessions.ts +5 -4
- package/src/runs/background/scheduled-runs.ts +40 -13
- package/src/runs/background/stale-run-reconciler.ts +3 -1
- package/src/runs/background/steering.ts +20 -2
- package/src/runs/background/subagent-runner.ts +458 -239
- package/src/runs/background/subagent-wait.ts +54 -8
- package/src/runs/background/wait-completions.ts +4 -0
- package/src/runs/background/wait-tool.ts +1 -1
- package/src/runs/foreground/async-steering-action.ts +37 -7
- package/src/runs/foreground/execution.ts +145 -56
- package/src/runs/foreground/prompt-audit.ts +3 -1
- package/src/runs/foreground/subagent-executor.ts +584 -297
- package/src/runs/foreground/workflow-detach-reconcile.ts +10 -5
- package/src/runs/foreground/workflow-foreground-steering.ts +57 -2
- package/src/runs/shared/acceptance.ts +7 -4
- package/src/runs/shared/agent-contract.ts +1 -1
- package/src/runs/shared/async-status-projection.ts +51 -47
- package/src/runs/shared/capability-ceiling.ts +2 -0
- package/src/runs/shared/child-hooks.ts +167 -3
- package/src/runs/shared/child-launch.ts +28 -13
- package/src/runs/shared/child-lifecycle.ts +6 -3
- package/src/runs/shared/child-runtime-config.ts +3 -1
- package/src/runs/shared/child-session.ts +75 -8
- package/src/runs/shared/child-tool-plan.ts +124 -5
- package/src/runs/shared/completion-evidence.ts +2 -2
- package/src/runs/shared/completion-guard.ts +6 -3
- package/src/runs/shared/effective-system-prompt.ts +33 -0
- package/src/runs/shared/external-cli-runner.ts +9 -7
- package/src/runs/shared/host-step-status.ts +11 -11
- package/src/runs/shared/llm-intent-arbiter.ts +21 -11
- package/src/runs/shared/model-fallback.ts +12 -6
- package/src/runs/shared/nested-events.ts +5 -5
- package/src/runs/shared/orca-progress-tabs.ts +7 -1
- package/src/runs/shared/parallel-handoff.ts +57 -12
- package/src/runs/shared/parallel-utils.ts +2 -2
- package/src/runs/shared/pi-spawn.ts +10 -0
- package/src/runs/shared/readonly-drain-observation.ts +42 -0
- package/src/runs/shared/readonly-model-continuation.ts +69 -0
- package/src/runs/shared/readonly-session-evidence.ts +307 -0
- package/src/runs/shared/run-fanout-budget.ts +8 -8
- package/src/runs/shared/runtime-acknowledged-extensions.ts +3 -3
- package/src/runs/shared/subagent-prompt-runtime.ts +20 -4
- package/src/runs/shared/task-intent.ts +46 -13
- package/src/runs/shared/workflow-async-child-guidance.ts +18 -0
- package/src/runs/shared/worktree-setup-command.ts +190 -0
- package/src/runs/shared/worktree.ts +366 -208
- package/src/shared/fork-context.ts +15 -72
- package/src/shared/launch-contract.ts +65 -2
- package/src/shared/opencode-session-headers.ts +30 -0
- package/src/shared/types.ts +85 -61
- package/src/shared/utils.ts +7 -2
- package/src/shared/workflow-child-permit.ts +18 -13
- package/src/slash/delegation-adapters.ts +3 -1
- package/src/slash/delegation-request.ts +14 -0
- package/src/slash/slash-commands.ts +2 -1
- package/src/slash/subagents-admin.ts +11 -4
- package/src/tui/fleet-status.ts +164 -19
- package/src/tui/fleet.ts +27 -19
- package/src/tui/render.ts +172 -33
- package/src/watchdog/child-status.ts +8 -0
- package/src/watchdog/model-selection.ts +20 -0
- package/src/watchdog/permission-arbiter.ts +3 -1
- package/src/watchdog/register-child.ts +1 -0
- package/src/watchdog/register-main.ts +31 -27
- package/src/watchdog/review.ts +132 -67
- package/src/watchdog/runtime.ts +82 -20
- package/src/watchdog/scope.ts +1 -1
- package/src/watchdog/settings.ts +9 -3
- package/src/watchdog/tool-actions.ts +13 -12
- package/src/watchdog/turn-delta.ts +23 -0
- package/src/watchdog/types.ts +4 -0
- package/src/workflows/chat-progress.ts +3 -3
- package/src/workflows/scripted-workflow.ts +275 -17
- package/src/workflows/workflow-checklist.ts +13 -17
- package/src/workflows/workflow-child-summary.ts +57 -8
- package/src/workflows/workflow-preflight.ts +19 -19
- package/src/workflows/workflow-receipt.ts +3 -3
- package/src/workflows/workflow-resources.ts +96 -21
- package/src/workflows/workflow-settlement.ts +3 -0
- /package/src/inspectors/{herdr/shell-command.ts → shell-command.ts} +0 -0
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
* the parent aliases that specifier (and the other host peer packages) to the
|
|
6
6
|
* installed pi package through `JITI_ALIAS` when it spawns the runner, see
|
|
7
7
|
* `runner-aliases.ts`. Tests replace the factory with a scripted one by
|
|
8
|
-
* naming a module in the runner config.
|
|
8
|
+
* naming a module in the runner config. The binary bootstrap supplies its
|
|
9
|
+
* embedded SDK through the default factory's existing loader option instead.
|
|
9
10
|
*/
|
|
10
11
|
import * as path from "node:path";
|
|
11
12
|
import { pathToFileURL } from "node:url";
|
|
12
|
-
import { createDefaultChildSessionFactory, type ChildSessionFactory } from "../shared/child-session.ts";
|
|
13
|
+
import { createDefaultChildSessionFactory, type ChildSessionFactory, type DefaultChildSessionFactoryOptions } from "../shared/child-session.ts";
|
|
13
14
|
|
|
14
15
|
export interface RunnerChildSessionConfig {
|
|
15
16
|
/** Test seam: module whose default export is a `ChildSessionFactory`, or a function returning one. */
|
|
@@ -20,8 +21,8 @@ function isChildSessionFactory(value: unknown): value is ChildSessionFactory {
|
|
|
20
21
|
return Boolean(value) && typeof value === "object" && typeof (value as ChildSessionFactory).create === "function" && typeof (value as ChildSessionFactory).dispose === "function";
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
export async function loadRunnerChildSessionFactory(config: RunnerChildSessionConfig): Promise<ChildSessionFactory> {
|
|
24
|
-
if (!config.childSessionFactoryModule) return createDefaultChildSessionFactory();
|
|
24
|
+
export async function loadRunnerChildSessionFactory(config: RunnerChildSessionConfig, options?: DefaultChildSessionFactoryOptions): Promise<ChildSessionFactory> {
|
|
25
|
+
if (!config.childSessionFactoryModule) return createDefaultChildSessionFactory(options);
|
|
25
26
|
const loaded = await import(pathToFileURL(path.resolve(config.childSessionFactoryModule)).href) as { default?: unknown };
|
|
26
27
|
const candidate = typeof loaded.default === "function" ? (loaded.default as () => unknown)() : loaded.default;
|
|
27
28
|
if (!isChildSessionFactory(candidate)) {
|
|
@@ -54,6 +54,7 @@ export interface ScheduleRecord {
|
|
|
54
54
|
timeoutMs?: number;
|
|
55
55
|
paused: boolean;
|
|
56
56
|
sessionOnly?: boolean;
|
|
57
|
+
quiet?: boolean;
|
|
57
58
|
ownerSessionFile?: string;
|
|
58
59
|
createdAt: string;
|
|
59
60
|
updatedAt: string;
|
|
@@ -308,6 +309,7 @@ function parseSchedule(value: unknown, file: string): ScheduleRecord {
|
|
|
308
309
|
if (typeof record.trigger.every !== "string" || typeof record.trigger.everyMs !== "number" || typeof record.trigger.anchorAt !== "string" || typeof record.trigger.nextRunAt !== "string") throw new Error(`Schedule record '${file}' has an invalid interval trigger.`);
|
|
309
310
|
} else throw new Error(`Schedule record '${file}' has an unsupported trigger.`);
|
|
310
311
|
if (record.sessionOnly !== undefined && typeof record.sessionOnly !== "boolean") throw new Error(`Schedule record '${file}' has invalid sessionOnly.`);
|
|
312
|
+
if (record.quiet !== undefined && typeof record.quiet !== "boolean") throw new Error(`Schedule record '${file}' has invalid quiet.`);
|
|
311
313
|
if (record.sessionOnly === true && (typeof record.ownerSessionFile !== "string" || !record.ownerSessionFile.trim())) throw new Error(`Schedule record '${file}' is session-only but has no owner session file.`);
|
|
312
314
|
return { ...record, target: parseScheduleTarget(record.target, file) } as ScheduleRecord;
|
|
313
315
|
}
|
|
@@ -448,7 +450,7 @@ function sanitizeTarget(params: SubagentParamsLike): { target?: ScheduleTarget;
|
|
|
448
450
|
return { target: { workflowScript: params.workflowScript.trim(), ...(baseRef === undefined ? {} : { baseRef }) } };
|
|
449
451
|
}
|
|
450
452
|
|
|
451
|
-
function executionParams(schedule: ScheduleRecord): SubagentParamsLike {
|
|
453
|
+
function executionParams(schedule: ScheduleRecord, quiet = false): SubagentParamsLike {
|
|
452
454
|
return {
|
|
453
455
|
...schedule.target,
|
|
454
456
|
async: true,
|
|
@@ -456,7 +458,7 @@ function executionParams(schedule: ScheduleRecord): SubagentParamsLike {
|
|
|
456
458
|
cwd: schedule.cwd,
|
|
457
459
|
mission: false,
|
|
458
460
|
// Scheduled fires have no operator watching, so completions must name the origin.
|
|
459
|
-
scheduleOrigin: { id: schedule.id, ...(schedule.name ? { name: schedule.name } : {}) },
|
|
461
|
+
scheduleOrigin: { id: schedule.id, ...(schedule.name ? { name: schedule.name } : {}), ...(quiet ? { quiet: true } : {}) },
|
|
460
462
|
...(schedule.timeoutMs === undefined ? {} : { timeoutMs: schedule.timeoutMs }),
|
|
461
463
|
};
|
|
462
464
|
}
|
|
@@ -612,6 +614,8 @@ export class ScheduledRunManager {
|
|
|
612
614
|
if (params.catchUp !== undefined && params.catchUp !== "none" && params.catchUp !== "latest") return textResult("catchUp must be 'none' or 'latest'.", undefined, undefined, true);
|
|
613
615
|
if (params.missionId !== undefined || params.mission !== undefined || params.missionUpdate !== undefined || params.missionStatus !== undefined || params.missionScope !== undefined) return textResult("Mission attachment is deferred from this first schedule slice.", undefined, undefined, true);
|
|
614
616
|
if (params.on !== undefined || params.timezone !== undefined || every === "day" || every === "week" || every === "month" || every === "year") return textResult("Calendar schedules are deferred from this first safe slice. Use a fixed interval such as every:'24h' or every:'7d'.", undefined, undefined, true);
|
|
617
|
+
if (params.quiet !== undefined && typeof params.quiet !== "boolean") return textResult("quiet must be a boolean.", undefined, undefined, true);
|
|
618
|
+
if (at && params.quiet === true) return textResult("quiet is only supported for recurring schedules.", undefined, undefined, true);
|
|
615
619
|
const sessionOnly = params.sessionOnly === true;
|
|
616
620
|
if (sessionOnly && params.cwd !== undefined && !samePath(params.cwd, ctx.cwd)) return textResult("sessionOnly schedules cannot use an explicit cross-project cwd.", undefined, undefined, true);
|
|
617
621
|
const ownerSessionFile = sessionOnly ? ctx.sessionManager.getSessionFile() : undefined;
|
|
@@ -644,13 +648,14 @@ export class ScheduledRunManager {
|
|
|
644
648
|
...(params.timeoutMs === undefined ? {} : { timeoutMs: params.timeoutMs }),
|
|
645
649
|
paused: false,
|
|
646
650
|
...(sessionOnly ? { sessionOnly: true, ownerSessionFile: path.resolve(ownerSessionFile!) } : {}),
|
|
651
|
+
...(trigger.kind === "interval" && params.quiet === true ? { quiet: true } : {}),
|
|
647
652
|
createdAt: timestamp(now),
|
|
648
653
|
updatedAt: timestamp(now),
|
|
649
654
|
};
|
|
650
655
|
store.write(schedule);
|
|
651
656
|
store.appendEvent(schedule, "schedule.created");
|
|
652
657
|
this.arm(schedule, store);
|
|
653
|
-
return textResult(`Created schedule ${id}.\nName: ${schedule.name}\nTrigger: ${at ? `at ${at}` : `every ${every}`}\nSession only: ${schedule.sessionOnly === true ? "yes" : "no"}\nNext: ${schedule.trigger.nextRunAt}\nTarget: ${targetLabel(schedule.target)}`, [schedule]);
|
|
658
|
+
return textResult(`Created schedule ${id}.\nName: ${schedule.name}\nTrigger: ${at ? `at ${at}` : `every ${every}`}\nSession only: ${schedule.sessionOnly === true ? "yes" : "no"}\nQuiet: ${schedule.quiet === true ? "yes" : "no"}\nNext: ${schedule.trigger.nextRunAt}\nTarget: ${targetLabel(schedule.target)}`, [schedule]);
|
|
654
659
|
}
|
|
655
660
|
|
|
656
661
|
private list(): AgentToolResult<Details> {
|
|
@@ -661,7 +666,7 @@ export class ScheduledRunManager {
|
|
|
661
666
|
|
|
662
667
|
private show(params: SubagentParamsLike): AgentToolResult<Details> {
|
|
663
668
|
const schedule = this.resolve(params);
|
|
664
|
-
return textResult([`Schedule: ${schedule.id}`, `Name: ${schedule.name}`, `State: ${schedule.paused ? "paused" : schedule.activeRunId ? "running" : "scheduled"}`, `Session only: ${schedule.sessionOnly === true ? "yes" : "no"}`, `Target: ${targetLabel(schedule.target)}`, `CWD: ${shortenPath(schedule.cwd)}`, `Next: ${schedule.trigger.nextRunAt ?? "none"}`, `Catch up: ${schedule.catchUp}`, schedule.activeRunId ? `Active run: ${schedule.activeRunId}` : undefined].filter(Boolean).join("\n"), [schedule]);
|
|
669
|
+
return textResult([`Schedule: ${schedule.id}`, `Name: ${schedule.name}`, `State: ${schedule.paused ? "paused" : schedule.activeRunId ? "running" : "scheduled"}`, `Session only: ${schedule.sessionOnly === true ? "yes" : "no"}`, `Quiet: ${schedule.quiet === true ? "yes" : "no"}`, `Target: ${targetLabel(schedule.target)}`, `CWD: ${shortenPath(schedule.cwd)}`, `Next: ${schedule.trigger.nextRunAt ?? "none"}`, `Catch up: ${schedule.catchUp}`, schedule.activeRunId ? `Active run: ${schedule.activeRunId}` : undefined].filter(Boolean).join("\n"), [schedule]);
|
|
665
670
|
}
|
|
666
671
|
|
|
667
672
|
private history(params: SubagentParamsLike): AgentToolResult<Details> {
|
|
@@ -689,7 +694,18 @@ export class ScheduledRunManager {
|
|
|
689
694
|
if (!scheduleBelongsToSession(schedule, context)) {
|
|
690
695
|
return textResult(`Skipped schedule ${schedule.id}: current session is not its owner.`, [schedule]);
|
|
691
696
|
}
|
|
692
|
-
|
|
697
|
+
if (params.quiet !== undefined && typeof params.quiet !== "boolean") return textResult("quiet must be a boolean.", undefined, undefined, true);
|
|
698
|
+
const run = await this.launch(store, schedule, this.now(), "manual", false, params.quiet === true);
|
|
699
|
+
const updated = store.get(schedule.id);
|
|
700
|
+
if (run.state === "running") {
|
|
701
|
+
const now = this.now();
|
|
702
|
+
if (updated.trigger.kind === "interval") updated.trigger.nextRunAt = timestamp(now + updated.trigger.everyMs);
|
|
703
|
+
else updated.trigger.nextRunAt = undefined;
|
|
704
|
+
updated.updatedAt = timestamp(now);
|
|
705
|
+
store.write(updated);
|
|
706
|
+
store.appendEvent(updated, "schedule.manual_satisfied");
|
|
707
|
+
this.arm(updated, store);
|
|
708
|
+
}
|
|
693
709
|
return textResult(`Manual schedule run ${run.id}: ${run.state}${run.asyncId ? ` (async ${run.asyncId})` : ""}.`, [store.get(schedule.id)], [run], run.state === "failed_launch");
|
|
694
710
|
}
|
|
695
711
|
|
|
@@ -708,8 +724,16 @@ export class ScheduledRunManager {
|
|
|
708
724
|
|
|
709
725
|
private remove(params: SubagentParamsLike): AgentToolResult<Details> {
|
|
710
726
|
const schedule = this.resolve(params);
|
|
711
|
-
if (schedule.activeRunId) return textResult(`Schedule ${schedule.id} has active run ${schedule.activeRunId}; stop that run before deleting the schedule.`, [schedule], undefined, true);
|
|
712
727
|
const store = this.requireStore();
|
|
728
|
+
if (schedule.activeRunId) {
|
|
729
|
+
const run = store.history(schedule.id).find((item) => item.id === schedule.activeRunId);
|
|
730
|
+
let terminal = false;
|
|
731
|
+
if (run?.scheduleId === schedule.id && run.state === "running" && run.asyncId && run.asyncDir) {
|
|
732
|
+
const status = readJson(path.join(run.asyncDir, "status.json"), "async status") as Partial<AsyncStatus>;
|
|
733
|
+
terminal = status.runId === run.asyncId && typeof status.state === "string" && ["complete", "failed", "stopped", "rejected"].includes(status.state);
|
|
734
|
+
}
|
|
735
|
+
if (!terminal) return textResult(`Schedule ${schedule.id} has active run ${schedule.activeRunId}; stop that run before deleting the schedule.`, [schedule], undefined, true);
|
|
736
|
+
}
|
|
713
737
|
this.clearTimer(store, schedule.id);
|
|
714
738
|
store.appendEvent(schedule, "schedule.deleted");
|
|
715
739
|
store.delete(schedule.id);
|
|
@@ -807,8 +831,9 @@ export class ScheduledRunManager {
|
|
|
807
831
|
await this.launch(store, schedule, planned, "timer", true);
|
|
808
832
|
}
|
|
809
833
|
|
|
810
|
-
private async launch(store: ScheduleStore, schedule: ScheduleRecord, planned: number, dueReason: ScheduleRunRecord["dueReason"], advance: boolean): Promise<ScheduleRunRecord> {
|
|
834
|
+
private async launch(store: ScheduleStore, schedule: ScheduleRecord, planned: number, dueReason: ScheduleRunRecord["dueReason"], advance: boolean, quiet?: boolean): Promise<ScheduleRunRecord> {
|
|
811
835
|
const now = this.now();
|
|
836
|
+
const nextRunAtBeforeClaim = schedule.trigger.nextRunAt;
|
|
812
837
|
const run: ScheduleRunRecord = { schemaVersion: 1, id: this.randomId(), scheduleId: schedule.id, plannedAt: timestamp(planned), dueReason, state: "running", startedAt: timestamp(now) };
|
|
813
838
|
if (schedule.activeRunId) {
|
|
814
839
|
run.state = "skipped";
|
|
@@ -849,7 +874,7 @@ export class ScheduledRunManager {
|
|
|
849
874
|
store.write(schedule);
|
|
850
875
|
store.writeRun(schedule, run, "schedule.run.started");
|
|
851
876
|
try {
|
|
852
|
-
const result = await this.deps.launch(executionParams(schedule), this.requireContext(store), new AbortController().signal);
|
|
877
|
+
const result = await this.deps.launch(executionParams(schedule, dueReason === "manual" ? quiet === true : schedule.quiet === true), this.requireContext(store), new AbortController().signal);
|
|
853
878
|
const asyncId = result.details?.asyncId ?? result.details?.runId;
|
|
854
879
|
if (result.isError || !asyncId) throw new Error(result.content.find((item) => item.type === "text")?.text ?? "Scheduled launch failed.");
|
|
855
880
|
run.asyncId = asyncId;
|
|
@@ -862,12 +887,14 @@ export class ScheduledRunManager {
|
|
|
862
887
|
run.state = "failed_launch";
|
|
863
888
|
run.completedAt = timestamp(this.now());
|
|
864
889
|
run.error = error instanceof Error ? error.message : String(error);
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
890
|
+
const latest = store.get(schedule.id);
|
|
891
|
+
latest.activeRunId = undefined;
|
|
892
|
+
if (!advance && nextRunAtBeforeClaim) latest.trigger.nextRunAt = nextRunAtBeforeClaim;
|
|
893
|
+
latest.updatedAt = timestamp(this.now());
|
|
894
|
+
store.write(latest);
|
|
895
|
+
store.writeRun(latest, run, "schedule.run.failed");
|
|
869
896
|
fs.rmSync(lockPath, { force: true });
|
|
870
|
-
this.arm(
|
|
897
|
+
this.arm(latest, store);
|
|
871
898
|
return run;
|
|
872
899
|
}
|
|
873
900
|
}
|
|
@@ -9,6 +9,7 @@ import { resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
|
9
9
|
import { normalizeParallelGroups } from "./parallel-groups.ts";
|
|
10
10
|
import { nestedSummaryFromAsyncStatus, projectNestedEvents, resolveNestedAsyncDir, writeNestedEvent, type NestedRoute } from "../shared/nested-events.ts";
|
|
11
11
|
import { assertWorkflowGraphHostSteps } from "../shared/host-step-status.ts";
|
|
12
|
+
import type { RawDrainStatusObserver } from "../shared/readonly-drain-observation.ts";
|
|
12
13
|
|
|
13
14
|
export type PidLiveness = "alive" | "dead" | "unknown";
|
|
14
15
|
|
|
@@ -352,9 +353,10 @@ export function checkPidLiveness(pid: number, kill: KillFn = process.kill): PidL
|
|
|
352
353
|
}
|
|
353
354
|
}
|
|
354
355
|
|
|
355
|
-
export function reconcileAsyncRun(asyncDir: string, options: ReconcileAsyncRunOptions = {}): ReconcileAsyncRunResult {
|
|
356
|
+
export function reconcileAsyncRun(asyncDir: string, options: ReconcileAsyncRunOptions = {}, observeStatus?: RawDrainStatusObserver): ReconcileAsyncRunResult {
|
|
356
357
|
const now = options.now?.() ?? Date.now();
|
|
357
358
|
const status = readStatus(asyncDir);
|
|
359
|
+
observeStatus?.(status);
|
|
358
360
|
const startedStatus = !status && options.startedRun ? buildStartedStatus(asyncDir, options.startedRun, now) : undefined;
|
|
359
361
|
const effectiveStatus = status ?? startedStatus;
|
|
360
362
|
if (!effectiveStatus) return { status: null, repaired: false };
|
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
import { readStatus } from "../../shared/utils.ts";
|
|
15
15
|
import { previewDisplayText } from "../../shared/display-text.ts";
|
|
16
16
|
import { redactSecretValues } from "../shared/permissions.ts";
|
|
17
|
+
import type { SteerDeliveryMode } from "./control-channel.ts";
|
|
17
18
|
|
|
18
19
|
export const MAX_STEERING_REQUESTS = 20;
|
|
19
20
|
export const STEERING_MESSAGE_PREVIEW_LIMIT = 160;
|
|
@@ -22,6 +23,21 @@ export function steeringMessagePreview(message: string): string {
|
|
|
22
23
|
return previewDisplayText(redactSecretValues(message), STEERING_MESSAGE_PREVIEW_LIMIT);
|
|
23
24
|
}
|
|
24
25
|
|
|
26
|
+
/** FIFO match of one accepted steer to an emitted user message; equal text claims the oldest entry. */
|
|
27
|
+
export function takeMatchingAcceptedSteer<T extends { text: string }>(accepted: T[], messageText: string): T | undefined {
|
|
28
|
+
const index = accepted.findIndex((entry) => entry.text === messageText);
|
|
29
|
+
if (index < 0) return undefined;
|
|
30
|
+
const [entry] = accepted.splice(index, 1);
|
|
31
|
+
return entry;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Settlement reason for one accepted request that never got a matching user `message_end`. */
|
|
35
|
+
export function unconsumedSteerReason(mode?: SteerDeliveryMode): string {
|
|
36
|
+
return mode === "follow_up"
|
|
37
|
+
? "child completed before consuming follow-up"
|
|
38
|
+
: "child completed before consuming steering";
|
|
39
|
+
}
|
|
40
|
+
|
|
25
41
|
export function steeringReceipt(message: string, receipt: string): string {
|
|
26
42
|
const preview = steeringMessagePreview(message);
|
|
27
43
|
const longestFence = Math.max(2, ...[...preview.matchAll(/`{3,}/g)].map((match) => match[0]!.length));
|
|
@@ -93,7 +109,9 @@ export function updateSteeringTarget(
|
|
|
93
109
|
if (fields.replacementRunId) target.replacementRunId = fields.replacementRunId;
|
|
94
110
|
return target;
|
|
95
111
|
}
|
|
96
|
-
|
|
112
|
+
const wasPending = target.state === "routed" || target.state === "queued";
|
|
113
|
+
const nowPending = state === "routed" || state === "queued";
|
|
114
|
+
if (wasPending && !nowPending) status.pending = Math.max(0, status.pending - 1);
|
|
97
115
|
target.state = state;
|
|
98
116
|
if (state === "routed") target.routedAt = now;
|
|
99
117
|
if (state === "delivered") {
|
|
@@ -108,7 +126,7 @@ export function updateSteeringTarget(
|
|
|
108
126
|
if (state === "recovered") target.recoveredAt = now;
|
|
109
127
|
if (fields.reason) target.reason = fields.reason;
|
|
110
128
|
if (fields.replacementRunId) target.replacementRunId = fields.replacementRunId;
|
|
111
|
-
incrementStateCount(status, state);
|
|
129
|
+
if (!wasPending || !nowPending) incrementStateCount(status, state);
|
|
112
130
|
return target;
|
|
113
131
|
}
|
|
114
132
|
|