@elie-laloum/outpost 7.0.0 → 8.0.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 +15 -0
- package/README.md +2 -0
- package/dist/adapters/agents/antigravity-adapter.js +3 -0
- package/dist/adapters/agents/antigravity-adapter.js.map +1 -1
- package/dist/adapters/agents/claude-adapter.js +3 -0
- package/dist/adapters/agents/claude-adapter.js.map +1 -1
- package/dist/adapters/agents/claude-events.js +25 -1
- package/dist/adapters/agents/claude-events.js.map +1 -1
- package/dist/adapters/agents/codex-adapter.js +3 -0
- package/dist/adapters/agents/codex-adapter.js.map +1 -1
- package/dist/adapters/agents/copilot-adapter.js +3 -0
- package/dist/adapters/agents/copilot-adapter.js.map +1 -1
- package/dist/adapters/agents/copilot-events.js +7 -3
- package/dist/adapters/agents/copilot-events.js.map +1 -1
- package/dist/adapters/agents/kimi-adapter.js +3 -0
- package/dist/adapters/agents/kimi-adapter.js.map +1 -1
- package/dist/adapters/agents/quota.constants.d.ts +7 -0
- package/dist/adapters/agents/quota.constants.js +31 -0
- package/dist/adapters/agents/quota.constants.js.map +1 -0
- package/dist/adapters/agents/quota.d.ts +1 -0
- package/dist/adapters/agents/quota.js +4 -0
- package/dist/adapters/agents/quota.js.map +1 -0
- package/dist/adapters/models/http-provider.js +8 -2
- package/dist/adapters/models/http-provider.js.map +1 -1
- package/dist/adapters/models/model.constants.d.ts +2 -0
- package/dist/adapters/models/model.constants.js +6 -0
- package/dist/adapters/models/model.constants.js.map +1 -1
- package/dist/adapters/models/stream-events.js +11 -7
- package/dist/adapters/models/stream-events.js.map +1 -1
- package/dist/application/agent-output.js +10 -1
- package/dist/application/agent-output.js.map +1 -1
- package/dist/application/agent-quota.d.ts +7 -0
- package/dist/application/agent-quota.js +33 -0
- package/dist/application/agent-quota.js.map +1 -0
- package/dist/application/agent-turn.js +25 -9
- package/dist/application/agent-turn.js.map +1 -1
- package/dist/application/dispatch-observation.js +10 -1
- package/dist/application/dispatch-observation.js.map +1 -1
- package/dist/application/execution.types.d.ts +2 -0
- package/dist/application/execution.types.js.map +1 -1
- package/dist/application/interactive-task-execution.d.ts +3 -0
- package/dist/application/interactive-task-execution.js +76 -0
- package/dist/application/interactive-task-execution.js.map +1 -0
- package/dist/application/interactive-task-protocol.d.ts +4 -0
- package/dist/application/interactive-task-protocol.js +56 -0
- package/dist/application/interactive-task-protocol.js.map +1 -0
- package/dist/application/interactive-task.constants.d.ts +4 -0
- package/dist/application/interactive-task.constants.js +7 -0
- package/dist/application/interactive-task.constants.js.map +1 -0
- package/dist/application/interactive-task.d.ts +3 -0
- package/dist/application/interactive-task.js +84 -0
- package/dist/application/interactive-task.js.map +1 -0
- package/dist/application/interactive-task.types.d.ts +44 -0
- package/dist/application/interactive-task.types.js +2 -0
- package/dist/application/interactive-task.types.js.map +1 -0
- package/dist/application/queue-worker.js +19 -1
- package/dist/application/queue-worker.js.map +1 -1
- package/dist/application/queued-task.js +17 -2
- package/dist/application/queued-task.js.map +1 -1
- package/dist/application/quota-resume.constants.d.ts +1 -0
- package/dist/application/quota-resume.constants.js +2 -0
- package/dist/application/quota-resume.constants.js.map +1 -0
- package/dist/application/quota-resume.d.ts +9 -0
- package/dist/application/quota-resume.js +34 -0
- package/dist/application/quota-resume.js.map +1 -0
- package/dist/application/quota-resume.types.d.ts +1 -0
- package/dist/application/quota-resume.types.js +2 -0
- package/dist/application/quota-resume.types.js.map +1 -0
- package/dist/application/recovery-retention-policy.js +1 -1
- package/dist/application/recovery-retention-policy.js.map +1 -1
- package/dist/application/recovery-retention.js +3 -3
- package/dist/application/recovery-retention.js.map +1 -1
- package/dist/application/recovery-retention.types.d.ts +1 -1
- package/dist/application/recovery-retention.types.js.map +1 -1
- package/dist/application/remote-workspace.js +2 -1
- package/dist/application/remote-workspace.js.map +1 -1
- package/dist/application/replay-recording.d.ts +2 -0
- package/dist/application/replay-recording.js +22 -0
- package/dist/application/replay-recording.js.map +1 -0
- package/dist/application/replay-turn.d.ts +4 -0
- package/dist/application/replay-turn.js +47 -0
- package/dist/application/replay-turn.js.map +1 -0
- package/dist/application/replay-workspace.d.ts +4 -0
- package/dist/application/replay-workspace.js +71 -0
- package/dist/application/replay-workspace.js.map +1 -0
- package/dist/application/replay-workspace.types.d.ts +6 -0
- package/dist/application/replay-workspace.types.js +2 -0
- package/dist/application/replay-workspace.types.js.map +1 -0
- package/dist/application/repository-fingerprint.d.ts +1 -0
- package/dist/application/repository-fingerprint.js +18 -0
- package/dist/application/repository-fingerprint.js.map +1 -0
- package/dist/application/sandbox-dispatch.js +2 -0
- package/dist/application/sandbox-dispatch.js.map +1 -1
- package/dist/application/speculation-checkpoint.js +19 -2
- package/dist/application/speculation-checkpoint.js.map +1 -1
- package/dist/application/speculation-checkpoint.types.d.ts +1 -1
- package/dist/application/speculation-checkpoint.types.js.map +1 -1
- package/dist/application/speculation-resume.js +21 -0
- package/dist/application/speculation-resume.js.map +1 -1
- package/dist/application/speculation-run.js +18 -1
- package/dist/application/speculation-run.js.map +1 -1
- package/dist/application/speculation.types.d.ts +7 -2
- package/dist/application/speculation.types.js.map +1 -1
- package/dist/application/tasks.d.ts +2 -2
- package/dist/application/tasks.js +11 -4
- package/dist/application/tasks.js.map +1 -1
- package/dist/application/tasks.types.d.ts +5 -0
- package/dist/application/tasks.types.js.map +1 -1
- package/dist/application/transport-inspection.constants.js +1 -0
- package/dist/application/transport-inspection.constants.js.map +1 -1
- package/dist/application/transport-retention.js +33 -1
- package/dist/application/transport-retention.js.map +1 -1
- package/dist/domain/agent-observation.js +1 -0
- package/dist/domain/agent-observation.js.map +1 -1
- package/dist/domain/agent.types.d.ts +8 -1
- package/dist/domain/agent.types.js.map +1 -1
- package/dist/domain/errors.types.d.ts +1 -1
- package/dist/domain/errors.types.js.map +1 -1
- package/dist/domain/observation.types.d.ts +6 -1
- package/dist/domain/observation.types.js.map +1 -1
- package/dist/domain/quota.constants.d.ts +1 -0
- package/dist/domain/quota.constants.js +2 -0
- package/dist/domain/quota.constants.js.map +1 -0
- package/dist/domain/quota.d.ts +4 -0
- package/dist/domain/quota.js +28 -0
- package/dist/domain/quota.js.map +1 -0
- package/dist/domain/quota.types.d.ts +7 -0
- package/dist/domain/quota.types.js +2 -0
- package/dist/domain/quota.types.js.map +1 -0
- package/dist/domain/replay.constants.d.ts +11 -0
- package/dist/domain/replay.constants.js +42 -0
- package/dist/domain/replay.constants.js.map +1 -0
- package/dist/domain/replay.d.ts +11 -0
- package/dist/domain/replay.js +231 -0
- package/dist/domain/replay.js.map +1 -0
- package/dist/domain/replay.types.d.ts +84 -0
- package/dist/domain/replay.types.js +2 -0
- package/dist/domain/replay.types.js.map +1 -0
- package/dist/domain/task-queue.js +18 -0
- package/dist/domain/task-queue.js.map +1 -1
- package/dist/domain/task-queue.types.d.ts +9 -0
- package/dist/domain/task-queue.types.js.map +1 -1
- package/dist/domain/workflow/checkpoint-validation.js +14 -1
- package/dist/domain/workflow/checkpoint-validation.js.map +1 -1
- package/dist/domain/workflow/checkpoint.js +13 -2
- package/dist/domain/workflow/checkpoint.js.map +1 -1
- package/dist/domain/workflow/gate-validation.js +2 -1
- package/dist/domain/workflow/gate-validation.js.map +1 -1
- package/dist/domain/workflow/input-validation.d.ts +7 -0
- package/dist/domain/workflow/input-validation.js +82 -0
- package/dist/domain/workflow/input-validation.js.map +1 -0
- package/dist/domain/workflow/input.d.ts +10 -0
- package/dist/domain/workflow/input.js +93 -0
- package/dist/domain/workflow/input.js.map +1 -0
- package/dist/domain/workflow/input.types.d.ts +37 -0
- package/dist/domain/workflow/input.types.js +2 -0
- package/dist/domain/workflow/input.types.js.map +1 -0
- package/dist/domain/workflow/loop-runner.d.ts +3 -0
- package/dist/domain/workflow/loop-runner.js +91 -0
- package/dist/domain/workflow/loop-runner.js.map +1 -0
- package/dist/domain/workflow/loop-task.d.ts +10 -0
- package/dist/domain/workflow/loop-task.js +39 -0
- package/dist/domain/workflow/loop-task.js.map +1 -0
- package/dist/domain/workflow/loop-task.types.d.ts +28 -0
- package/dist/domain/workflow/loop-task.types.js +2 -0
- package/dist/domain/workflow/loop-task.types.js.map +1 -0
- package/dist/domain/workflow/loop-validation.d.ts +4 -0
- package/dist/domain/workflow/loop-validation.js +62 -0
- package/dist/domain/workflow/loop-validation.js.map +1 -0
- package/dist/domain/workflow/quota-pause.d.ts +7 -0
- package/dist/domain/workflow/quota-pause.js +83 -0
- package/dist/domain/workflow/quota-pause.js.map +1 -0
- package/dist/domain/workflow/quota-pause.types.d.ts +14 -0
- package/dist/domain/workflow/quota-pause.types.js +2 -0
- package/dist/domain/workflow/quota-pause.types.js.map +1 -0
- package/dist/domain/workflow/quota-validation.d.ts +2 -0
- package/dist/domain/workflow/quota-validation.js +30 -0
- package/dist/domain/workflow/quota-validation.js.map +1 -0
- package/dist/domain/workflow/task-cache-entry.d.ts +3 -0
- package/dist/domain/workflow/task-cache-entry.js +57 -0
- package/dist/domain/workflow/task-cache-entry.js.map +1 -0
- package/dist/domain/workflow/task-cache.constants.d.ts +3 -0
- package/dist/domain/workflow/task-cache.constants.js +3 -0
- package/dist/domain/workflow/task-cache.constants.js.map +1 -0
- package/dist/domain/workflow/task-cache.d.ts +6 -0
- package/dist/domain/workflow/task-cache.js +92 -0
- package/dist/domain/workflow/task-cache.js.map +1 -0
- package/dist/domain/workflow/task-cache.types.d.ts +35 -0
- package/dist/domain/workflow/task-cache.types.js +2 -0
- package/dist/domain/workflow/task-cache.types.js.map +1 -0
- package/dist/domain/workflow/task-runner.js +100 -34
- package/dist/domain/workflow/task-runner.js.map +1 -1
- package/dist/domain/workflow/task.js +21 -0
- package/dist/domain/workflow/task.js.map +1 -1
- package/dist/domain/workflow/validation.js +13 -2
- package/dist/domain/workflow/validation.js.map +1 -1
- package/dist/domain/workflow/workflow-scheduler.js +27 -2
- package/dist/domain/workflow/workflow-scheduler.js.map +1 -1
- package/dist/domain/workflow/workflow-state.js +20 -3
- package/dist/domain/workflow/workflow-state.js.map +1 -1
- package/dist/domain/workflow.types.d.ts +26 -3
- package/dist/domain/workflow.types.js.map +1 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/git/command.d.ts +2 -1
- package/dist/infrastructure/git/command.js +2 -2
- package/dist/infrastructure/git/command.js.map +1 -1
- package/dist/infrastructure/git/replay-commits.constants.d.ts +5 -0
- package/dist/infrastructure/git/replay-commits.constants.js +6 -0
- package/dist/infrastructure/git/replay-commits.constants.js.map +1 -0
- package/dist/infrastructure/git/replay-commits.d.ts +2 -0
- package/dist/infrastructure/git/replay-commits.js +92 -0
- package/dist/infrastructure/git/replay-commits.js.map +1 -0
- package/dist/infrastructure/journal.types.d.ts +1 -0
- package/dist/infrastructure/journal.types.js.map +1 -1
- package/dist/infrastructure/reporter.js +3 -0
- package/dist/infrastructure/reporter.js.map +1 -1
- package/dist/infrastructure/storage-inventory.types.d.ts +1 -1
- package/dist/infrastructure/storage-inventory.types.js.map +1 -1
- package/dist/infrastructure/transport-task-cache.constants.d.ts +2 -0
- package/dist/infrastructure/transport-task-cache.constants.js +3 -0
- package/dist/infrastructure/transport-task-cache.constants.js.map +1 -0
- package/dist/infrastructure/transport-task-cache.d.ts +3 -0
- package/dist/infrastructure/transport-task-cache.js +56 -0
- package/dist/infrastructure/transport-task-cache.js.map +1 -0
- package/dist/infrastructure/transport-task-cache.types.d.ts +4 -0
- package/dist/infrastructure/transport-task-cache.types.js +2 -0
- package/dist/infrastructure/transport-task-cache.types.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { executeInteractiveTurn } from "./interactive-task-execution.js";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { invariant } from "../domain/errors.js";
|
|
5
|
+
import { task } from "../domain/workflow/task.js";
|
|
6
|
+
import { positive } from "../domain/workflow/validation.js";
|
|
7
|
+
import { directory } from "../infrastructure/files.js";
|
|
8
|
+
import { git } from "../infrastructure/git/command.js";
|
|
9
|
+
import { storageFor } from "./agent-storage.js";
|
|
10
|
+
import { interactiveTaskDefaults } from "./interactive-task.constants.js";
|
|
11
|
+
import { interactiveState } from "./interactive-task-protocol.js";
|
|
12
|
+
export function interactiveAgentTask(options) {
|
|
13
|
+
invariant(typeof options.repository === "string" && !!options.repository.trim(), "Interactive tasks require a repository");
|
|
14
|
+
invariant(typeof options.brief === "string" && !!options.brief.trim(), "Interactive tasks require a brief");
|
|
15
|
+
const maxTurns = options.maxTurns ?? interactiveTaskDefaults.maxTurns;
|
|
16
|
+
positive(maxTurns, "maxTurns");
|
|
17
|
+
const selected = options.agent;
|
|
18
|
+
invariant(selected.resumable !== false &&
|
|
19
|
+
selected.capture !== false &&
|
|
20
|
+
storageFor(selected), "Interactive tasks require portable conversation capture and resume");
|
|
21
|
+
const settings = {
|
|
22
|
+
...options,
|
|
23
|
+
repository: resolve(options.repository),
|
|
24
|
+
maxTurns,
|
|
25
|
+
};
|
|
26
|
+
const identity = createHash("sha256")
|
|
27
|
+
.update(JSON.stringify({
|
|
28
|
+
repository: settings.repository,
|
|
29
|
+
agent: selected.name,
|
|
30
|
+
model: "model" in selected ? selected.model : undefined,
|
|
31
|
+
brief: options.brief,
|
|
32
|
+
maxTurns,
|
|
33
|
+
conversationHome: options.conversationHome,
|
|
34
|
+
provider: options.sandboxProvider?.name,
|
|
35
|
+
}))
|
|
36
|
+
.digest("hex");
|
|
37
|
+
return task({
|
|
38
|
+
key: options.key,
|
|
39
|
+
...(options.after ? { after: options.after } : {}),
|
|
40
|
+
...(options.timeoutMs !== undefined
|
|
41
|
+
? { timeoutMs: options.timeoutMs }
|
|
42
|
+
: {}),
|
|
43
|
+
interaction: { identity, actors: options.actors },
|
|
44
|
+
perform: (context) => interactiveTurn(settings, context),
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
async function interactiveTurn(options, context) {
|
|
48
|
+
const interaction = context.interaction;
|
|
49
|
+
invariant(interaction, "Interactive tasks must run in a checkpointed workflow");
|
|
50
|
+
const previous = interactiveState(interaction.state);
|
|
51
|
+
if (previous?.completed)
|
|
52
|
+
return previous.completed;
|
|
53
|
+
invariant((previous?.turns ?? 0) < options.maxTurns, "Interactive task exceeded maxTurns");
|
|
54
|
+
if (previous?.conversation)
|
|
55
|
+
invariant(interaction.answer, "A human answer is required to continue");
|
|
56
|
+
const branch = `outpost/interactive-${createHash("sha256")
|
|
57
|
+
.update(JSON.stringify([context.executionId, options.key]))
|
|
58
|
+
.digest("hex")
|
|
59
|
+
.slice(0, 24)}`;
|
|
60
|
+
if (previous) {
|
|
61
|
+
invariant(previous.branch === branch, "Interactive checkpoint branch mismatch");
|
|
62
|
+
await directory(previous.directory);
|
|
63
|
+
const attached = (await git(previous.directory, ["symbolic-ref", "--short", "HEAD"])).trim();
|
|
64
|
+
invariant(attached === branch, "Interactive workspace branch changed; recover it explicitly");
|
|
65
|
+
}
|
|
66
|
+
const { next, value } = await executeInteractiveTurn(options, context, branch, previous);
|
|
67
|
+
if (value.kind === "question") {
|
|
68
|
+
if (next.turns >= options.maxTurns) {
|
|
69
|
+
await interaction.save(next);
|
|
70
|
+
throw new Error("Interactive task exceeded maxTurns before completion");
|
|
71
|
+
}
|
|
72
|
+
return interaction.suspend(value, next);
|
|
73
|
+
}
|
|
74
|
+
const completed = {
|
|
75
|
+
output: value.output,
|
|
76
|
+
conversation: next.conversation,
|
|
77
|
+
branch,
|
|
78
|
+
directory: next.directory,
|
|
79
|
+
turns: next.turns,
|
|
80
|
+
};
|
|
81
|
+
await interaction.save({ ...next, completed });
|
|
82
|
+
return completed;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=interactive-task.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interactive-task.js","sourceRoot":"","sources":["../../src/application/interactive-task.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAChD,OAAO,EAAE,IAAI,EAAE,MAAM,4BAA4B,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAE5D,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACvD,OAAO,EAAE,GAAG,EAAE,MAAM,kCAAkC,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAC1E,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAMlE,MAAM,UAAU,oBAAoB,CAClC,OAAoC;IAEpC,SAAS,CACP,OAAO,OAAO,CAAC,UAAU,KAAK,QAAQ,IAAI,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,EAAE,EACrE,wCAAwC,CACzC,CAAC;IACF,SAAS,CACP,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,EAC3D,mCAAmC,CACpC,CAAC;IACF,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,uBAAuB,CAAC,QAAQ,CAAC;IACtE,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IAC/B,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC;IAC/B,SAAS,CACP,QAAQ,CAAC,SAAS,KAAK,KAAK;QAC1B,QAAQ,CAAC,OAAO,KAAK,KAAK;QAC1B,UAAU,CAAC,QAAQ,CAAC,EACtB,oEAAoE,CACrE,CAAC;IACF,MAAM,QAAQ,GAAG;QACf,GAAG,OAAO;QACV,UAAU,EAAE,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC;QACvC,QAAQ;KACT,CAAC;IACF,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC;SAClC,MAAM,CACL,IAAI,CAAC,SAAS,CAAC;QACb,UAAU,EAAE,QAAQ,CAAC,UAAU;QAC/B,KAAK,EAAE,QAAQ,CAAC,IAAI;QACpB,KAAK,EAAE,OAAO,IAAI,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS;QACvD,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,QAAQ;QACR,gBAAgB,EAAE,OAAO,CAAC,gBAAgB;QAC1C,QAAQ,EAAE,OAAO,CAAC,eAAe,EAAE,IAAI;KACxC,CAAC,CACH;SACA,MAAM,CAAC,KAAK,CAAC,CAAC;IACjB,OAAO,IAAI,CAAC;QACV,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAClD,GAAG,CAAC,OAAO,CAAC,SAAS,KAAK,SAAS;YACjC,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE;YAClC,CAAC,CAAC,EAAE,CAAC;QACP,WAAW,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE;QACjD,OAAO,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,eAAe,CAAC,QAAQ,EAAE,OAAO,CAAC;KACzD,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,eAAe,CAC5B,OACyD,EACzD,OAAoB;IAEpB,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;IACxC,SAAS,CACP,WAAW,EACX,uDAAuD,CACxD,CAAC;IACF,MAAM,QAAQ,GAAG,gBAAgB,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IACrD,IAAI,QAAQ,EAAE,SAAS;QAAE,OAAO,QAAQ,CAAC,SAAS,CAAC;IACnD,SAAS,CACP,CAAC,QAAQ,EAAE,KAAK,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC,QAAQ,EACzC,oCAAoC,CACrC,CAAC;IACF,IAAI,QAAQ,EAAE,YAAY;QACxB,SAAS,CAAC,WAAW,CAAC,MAAM,EAAE,wCAAwC,CAAC,CAAC;IAC1E,MAAM,MAAM,GAAG,uBAAuB,UAAU,CAAC,QAAQ,CAAC;SACvD,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;SAC1D,MAAM,CAAC,KAAK,CAAC;SACb,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;IAClB,IAAI,QAAQ,EAAE,CAAC;QACb,SAAS,CACP,QAAQ,CAAC,MAAM,KAAK,MAAM,EAC1B,wCAAwC,CACzC,CAAC;QACF,MAAM,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QACpC,MAAM,QAAQ,GAAG,CACf,MAAM,GAAG,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC,cAAc,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC,CACnE,CAAC,IAAI,EAAE,CAAC;QACT,SAAS,CACP,QAAQ,KAAK,MAAM,EACnB,6DAA6D,CAC9D,CAAC;IACJ,CAAC;IACD,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,sBAAsB,CAClD,OAAO,EACP,OAAO,EACP,MAAM,EACN,QAAQ,CACT,CAAC;IACF,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QAC9B,IAAI,IAAI,CAAC,KAAK,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YACnC,MAAM,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC7B,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;QAC1E,CAAC;QACD,OAAO,WAAW,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC1C,CAAC;IACD,MAAM,SAAS,GAA2B;QACxC,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,MAAM;QACN,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,KAAK,EAAE,IAAI,CAAC,KAAK;KAClB,CAAC;IACF,MAAM,WAAW,CAAC,IAAI,CAAC,EAAE,GAAG,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;IAC/C,OAAO,SAAS,CAAC;AACnB,CAAC","sourcesContent":["import { executeInteractiveTurn } from \"./interactive-task-execution.ts\";\nimport { createHash } from \"node:crypto\";\nimport { resolve } from \"node:path\";\nimport { invariant } from \"../domain/errors.ts\";\nimport { task } from \"../domain/workflow/task.ts\";\nimport { positive } from \"../domain/workflow/validation.ts\";\nimport type { Task, TaskContext } from \"../domain/workflow.types.ts\";\nimport { directory } from \"../infrastructure/files.ts\";\nimport { git } from \"../infrastructure/git/command.ts\";\nimport { storageFor } from \"./agent-storage.ts\";\nimport { interactiveTaskDefaults } from \"./interactive-task.constants.ts\";\nimport { interactiveState } from \"./interactive-task-protocol.ts\";\nimport type {\n InteractiveAgentTaskOptions,\n InteractiveAgentResult,\n} from \"./interactive-task.types.ts\";\n\nexport function interactiveAgentTask(\n options: InteractiveAgentTaskOptions,\n): Task<InteractiveAgentResult> {\n invariant(\n typeof options.repository === \"string\" && !!options.repository.trim(),\n \"Interactive tasks require a repository\",\n );\n invariant(\n typeof options.brief === \"string\" && !!options.brief.trim(),\n \"Interactive tasks require a brief\",\n );\n const maxTurns = options.maxTurns ?? interactiveTaskDefaults.maxTurns;\n positive(maxTurns, \"maxTurns\");\n const selected = options.agent;\n invariant(\n selected.resumable !== false &&\n selected.capture !== false &&\n storageFor(selected),\n \"Interactive tasks require portable conversation capture and resume\",\n );\n const settings = {\n ...options,\n repository: resolve(options.repository),\n maxTurns,\n };\n const identity = createHash(\"sha256\")\n .update(\n JSON.stringify({\n repository: settings.repository,\n agent: selected.name,\n model: \"model\" in selected ? selected.model : undefined,\n brief: options.brief,\n maxTurns,\n conversationHome: options.conversationHome,\n provider: options.sandboxProvider?.name,\n }),\n )\n .digest(\"hex\");\n return task({\n key: options.key,\n ...(options.after ? { after: options.after } : {}),\n ...(options.timeoutMs !== undefined\n ? { timeoutMs: options.timeoutMs }\n : {}),\n interaction: { identity, actors: options.actors },\n perform: (context) => interactiveTurn(settings, context),\n });\n}\n\nasync function interactiveTurn(\n options: InteractiveAgentTaskOptions &\n Required<Pick<InteractiveAgentTaskOptions, \"maxTurns\">>,\n context: TaskContext,\n): Promise<InteractiveAgentResult> {\n const interaction = context.interaction;\n invariant(\n interaction,\n \"Interactive tasks must run in a checkpointed workflow\",\n );\n const previous = interactiveState(interaction.state);\n if (previous?.completed) return previous.completed;\n invariant(\n (previous?.turns ?? 0) < options.maxTurns,\n \"Interactive task exceeded maxTurns\",\n );\n if (previous?.conversation)\n invariant(interaction.answer, \"A human answer is required to continue\");\n const branch = `outpost/interactive-${createHash(\"sha256\")\n .update(JSON.stringify([context.executionId, options.key]))\n .digest(\"hex\")\n .slice(0, 24)}`;\n if (previous) {\n invariant(\n previous.branch === branch,\n \"Interactive checkpoint branch mismatch\",\n );\n await directory(previous.directory);\n const attached = (\n await git(previous.directory, [\"symbolic-ref\", \"--short\", \"HEAD\"])\n ).trim();\n invariant(\n attached === branch,\n \"Interactive workspace branch changed; recover it explicitly\",\n );\n }\n const { next, value } = await executeInteractiveTurn(\n options,\n context,\n branch,\n previous,\n );\n if (value.kind === \"question\") {\n if (next.turns >= options.maxTurns) {\n await interaction.save(next);\n throw new Error(\"Interactive task exceeded maxTurns before completion\");\n }\n return interaction.suspend(value, next);\n }\n const completed: InteractiveAgentResult = {\n output: value.output,\n conversation: next.conversation,\n branch,\n directory: next.directory,\n turns: next.turns,\n };\n await interaction.save({ ...next, completed });\n return completed;\n}\n"]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Agent } from "../domain/agent.types.ts";
|
|
2
|
+
import type { SandboxProvider } from "../domain/sandbox.types.ts";
|
|
3
|
+
import type { Task } from "../domain/workflow.types.ts";
|
|
4
|
+
import type { WorkflowJson } from "../domain/workflow/checkpoint.types.ts";
|
|
5
|
+
import type { WorkflowInputQuestion } from "../domain/workflow/input.types.ts";
|
|
6
|
+
export interface InteractiveAgentTaskOptions {
|
|
7
|
+
readonly key: string;
|
|
8
|
+
readonly after?: readonly Task[];
|
|
9
|
+
readonly repository: string;
|
|
10
|
+
readonly agent: Agent;
|
|
11
|
+
readonly brief: string;
|
|
12
|
+
readonly actors: readonly string[];
|
|
13
|
+
readonly sandboxProvider?: SandboxProvider;
|
|
14
|
+
readonly bootstrap?: boolean;
|
|
15
|
+
readonly conversationHome?: string;
|
|
16
|
+
readonly maxTurns?: number;
|
|
17
|
+
readonly timeoutMs?: number;
|
|
18
|
+
}
|
|
19
|
+
export type InteractiveAgentResult = {
|
|
20
|
+
readonly output: WorkflowJson;
|
|
21
|
+
readonly conversation: string;
|
|
22
|
+
readonly branch: string;
|
|
23
|
+
readonly directory: string;
|
|
24
|
+
readonly turns: number;
|
|
25
|
+
};
|
|
26
|
+
export type InteractiveAgentTurn = ({
|
|
27
|
+
readonly kind: "question";
|
|
28
|
+
} & WorkflowInputQuestion) | {
|
|
29
|
+
readonly kind: "completed";
|
|
30
|
+
readonly output: WorkflowJson;
|
|
31
|
+
};
|
|
32
|
+
export type InteractiveAgentState = {
|
|
33
|
+
readonly turns: number;
|
|
34
|
+
readonly branch: string;
|
|
35
|
+
readonly directory: string;
|
|
36
|
+
readonly conversation?: string;
|
|
37
|
+
readonly completed?: InteractiveAgentResult;
|
|
38
|
+
};
|
|
39
|
+
export interface InteractiveAgentTurnResult {
|
|
40
|
+
readonly next: InteractiveAgentState & {
|
|
41
|
+
readonly conversation: string;
|
|
42
|
+
};
|
|
43
|
+
readonly value: InteractiveAgentTurn;
|
|
44
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interactive-task.types.js","sourceRoot":"","sources":["../../src/application/interactive-task.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Agent } from \"../domain/agent.types.ts\";\nimport type { SandboxProvider } from \"../domain/sandbox.types.ts\";\nimport type { Task } from \"../domain/workflow.types.ts\";\nimport type { WorkflowJson } from \"../domain/workflow/checkpoint.types.ts\";\nimport type { WorkflowInputQuestion } from \"../domain/workflow/input.types.ts\";\n\nexport interface InteractiveAgentTaskOptions {\n readonly key: string;\n readonly after?: readonly Task[];\n readonly repository: string;\n readonly agent: Agent;\n readonly brief: string;\n readonly actors: readonly string[];\n readonly sandboxProvider?: SandboxProvider;\n readonly bootstrap?: boolean;\n readonly conversationHome?: string;\n readonly maxTurns?: number;\n readonly timeoutMs?: number;\n}\n\nexport type InteractiveAgentResult = {\n readonly output: WorkflowJson;\n readonly conversation: string;\n readonly branch: string;\n readonly directory: string;\n readonly turns: number;\n};\n\nexport type InteractiveAgentTurn =\n | ({ readonly kind: \"question\" } & WorkflowInputQuestion)\n | { readonly kind: \"completed\"; readonly output: WorkflowJson };\n\nexport type InteractiveAgentState = {\n readonly turns: number;\n readonly branch: string;\n readonly directory: string;\n readonly conversation?: string;\n readonly completed?: InteractiveAgentResult;\n};\n\nexport interface InteractiveAgentTurnResult {\n readonly next: InteractiveAgentState & { readonly conversation: string };\n readonly value: InteractiveAgentTurn;\n}\n"]}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { setTimeout as delay } from "node:timers/promises";
|
|
2
2
|
import { queueMaxStringLength, queueDefaultLeaseMs, queueDefaultPollMs, } from "../domain/task-queue.constants.js";
|
|
3
3
|
import { queueLeaseMs, queueNumber, queueString, queueResult, } from "../domain/task-queue.js";
|
|
4
|
+
import { recoveryDetails } from "../domain/errors.js";
|
|
5
|
+
import { quotaFault } from "../domain/quota.js";
|
|
4
6
|
export async function runQueueWorker(options) {
|
|
5
7
|
const leaseMs = queueLeaseMs(options.leaseMs ?? queueDefaultLeaseMs);
|
|
6
8
|
const pollMs = queueNumber(options.pollMs ?? queueDefaultPollMs);
|
|
@@ -37,12 +39,28 @@ export async function runQueueWorker(options) {
|
|
|
37
39
|
throw new Error("Worker handler unavailable");
|
|
38
40
|
let result;
|
|
39
41
|
try {
|
|
40
|
-
result = queueResult(await handler(job.input, {
|
|
42
|
+
result = queueResult(await handler(job.input, {
|
|
43
|
+
signal,
|
|
44
|
+
job,
|
|
45
|
+
idempotencyKey: job.idempotencyKey ?? job.id,
|
|
46
|
+
}));
|
|
41
47
|
}
|
|
42
48
|
catch (error) {
|
|
49
|
+
const quota = quotaFault(error);
|
|
50
|
+
const captured = typeof recoveryDetails(error)?.transcript === "string" &&
|
|
51
|
+
quota?.conversation !== undefined &&
|
|
52
|
+
quota.conversation.length <= queueMaxStringLength;
|
|
43
53
|
result = {
|
|
44
54
|
value: null,
|
|
45
55
|
error: (error instanceof Error ? error.message : String(error)).slice(0, queueMaxStringLength) || "Queue handler failed",
|
|
56
|
+
...(quota
|
|
57
|
+
? {
|
|
58
|
+
quota: {
|
|
59
|
+
...(quota.resetAt ? { resetAt: quota.resetAt } : {}),
|
|
60
|
+
...(captured ? { conversation: quota.conversation } : {}),
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
: {}),
|
|
46
64
|
};
|
|
47
65
|
}
|
|
48
66
|
if (!signal.aborted)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queue-worker.js","sourceRoot":"","sources":["../../src/application/queue-worker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,IAAI,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EACL,YAAY,EACZ,WAAW,EACX,WAAW,EACX,WAAW,GACZ,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"queue-worker.js","sourceRoot":"","sources":["../../src/application/queue-worker.ts"],"names":[],"mappings":"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","sourcesContent":["import { setTimeout as delay } from \"node:timers/promises\";\nimport {\n queueMaxStringLength,\n queueDefaultLeaseMs,\n queueDefaultPollMs,\n} from \"../domain/task-queue.constants.ts\";\nimport {\n queueLeaseMs,\n queueNumber,\n queueString,\n queueResult,\n} from \"../domain/task-queue.ts\";\nimport type { QueueWorkerOptions } from \"./queue-worker.types.ts\";\nimport type { QueueJob, QueueResult } from \"../domain/task-queue.types.ts\";\nimport { recoveryDetails } from \"../domain/errors.ts\";\nimport { quotaFault } from \"../domain/quota.ts\";\n\nexport async function runQueueWorker(\n options: QueueWorkerOptions,\n): Promise<void> {\n const leaseMs = queueLeaseMs(options.leaseMs ?? queueDefaultLeaseMs);\n const pollMs = queueNumber(options.pollMs ?? queueDefaultPollMs);\n if (!pollMs) throw new Error(\"Worker poll interval must be positive\");\n const handlers = new Map(Object.entries(options.handlers));\n if (\n !handlers.size ||\n [...handlers.values()].some((handler) => typeof handler !== \"function\")\n )\n throw new Error(\"Worker requires trusted handlers\");\n const worker = queueString(options.worker);\n async function execute(job: QueueJob) {\n if (options.signal.aborted) return;\n const stop = new AbortController();\n const signal = AbortSignal.any([options.signal, stop.signal]);\n const lease = { id: job.id, worker, fence: job.fence };\n const heartbeat = (async () => {\n try {\n while (!signal.aborted) {\n await delay(Math.max(10, Math.floor(leaseMs / 3)), undefined, {\n signal,\n });\n await options.queue.renew(lease, leaseMs);\n }\n } catch (error) {\n if (!signal.aborted) stop.abort(error);\n }\n })();\n try {\n const handler = handlers.get(job.handler);\n if (!handler) throw new Error(\"Worker handler unavailable\");\n let result: QueueResult;\n try {\n result = queueResult(\n await handler(job.input, {\n signal,\n job,\n idempotencyKey: job.idempotencyKey ?? job.id,\n }),\n );\n } catch (error) {\n const quota = quotaFault(error);\n const captured =\n typeof recoveryDetails(error)?.transcript === \"string\" &&\n quota?.conversation !== undefined &&\n quota.conversation.length <= queueMaxStringLength;\n result = {\n value: null,\n error:\n (error instanceof Error ? error.message : String(error)).slice(\n 0,\n queueMaxStringLength,\n ) || \"Queue handler failed\",\n ...(quota\n ? {\n quota: {\n ...(quota.resetAt ? { resetAt: quota.resetAt } : {}),\n ...(captured ? { conversation: quota.conversation } : {}),\n },\n }\n : {}),\n };\n }\n if (!signal.aborted) await options.queue.complete(lease, result);\n } finally {\n stop.abort();\n await heartbeat;\n }\n }\n try {\n while (!options.signal.aborted) {\n const job = await options.queue.claim({\n worker,\n handlers: [...handlers.keys()],\n leaseMs,\n });\n if (job) {\n await execute(job);\n continue;\n }\n await delay(pollMs, undefined, { signal: options.signal });\n }\n } catch (error) {\n if (!options.signal.aborted) throw error;\n }\n}\n"]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { taskIdempotencyKey } from "../domain/workflow/idempotency.js";
|
|
2
2
|
import { setTimeout as delay } from "node:timers/promises";
|
|
3
|
+
import { OutpostError, recordRecovery } from "../domain/errors.js";
|
|
3
4
|
import { task } from "../domain/workflow.js";
|
|
4
5
|
import { queueDefaultPollMs } from "../domain/task-queue.constants.js";
|
|
5
6
|
import { queueNumber, queueString } from "../domain/task-queue.js";
|
|
@@ -14,9 +15,11 @@ export function queuedTask(options) {
|
|
|
14
15
|
...definition,
|
|
15
16
|
async perform(context) {
|
|
16
17
|
context.signal.throwIfAborted();
|
|
17
|
-
const
|
|
18
|
+
const key = taskIdempotencyKey(context.executionId, definition.key);
|
|
19
|
+
const id = context.quota ? `${key}:quota:${context.attempt}` : key;
|
|
18
20
|
let job = await queue.enqueue({
|
|
19
21
|
id,
|
|
22
|
+
...(context.quota ? { idempotencyKey: key } : {}),
|
|
20
23
|
handler,
|
|
21
24
|
input: input(context),
|
|
22
25
|
...(deadline === undefined ? {} : { deadline }),
|
|
@@ -46,7 +49,7 @@ export function queuedTask(options) {
|
|
|
46
49
|
context.reportUsage(job.result.usage);
|
|
47
50
|
}
|
|
48
51
|
if (job.status !== "done" || !job.result)
|
|
49
|
-
throw
|
|
52
|
+
throw queuedFailure(job.result?.error, job.result?.quota, id);
|
|
50
53
|
context.signal.throwIfAborted();
|
|
51
54
|
const value = decode(job.result.value);
|
|
52
55
|
context.observation?.emit("workflow", {
|
|
@@ -72,4 +75,16 @@ export function queuedTask(options) {
|
|
|
72
75
|
},
|
|
73
76
|
});
|
|
74
77
|
}
|
|
78
|
+
function queuedFailure(message, quota, job) {
|
|
79
|
+
if (!quota)
|
|
80
|
+
return new Error(message ?? "Queued workflow job cancelled");
|
|
81
|
+
const error = new OutpostError("quota", message ?? "Queued workflow job reached a usage limit", {
|
|
82
|
+
job,
|
|
83
|
+
...(quota.resetAt ? { resetAt: quota.resetAt } : {}),
|
|
84
|
+
...(quota.conversation ? { conversation: quota.conversation } : {}),
|
|
85
|
+
});
|
|
86
|
+
if (quota.conversation)
|
|
87
|
+
recordRecovery(error, { conversation: quota.conversation });
|
|
88
|
+
return error;
|
|
89
|
+
}
|
|
75
90
|
//# sourceMappingURL=queued-task.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queued-task.js","sourceRoot":"","sources":["../../src/application/queued-task.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AACvE,OAAO,EAAE,UAAU,IAAI,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAC7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AACvE,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"queued-task.js","sourceRoot":"","sources":["../../src/application/queued-task.ts"],"names":[],"mappings":"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","sourcesContent":["import { taskIdempotencyKey } from \"../domain/workflow/idempotency.ts\";\nimport { setTimeout as delay } from \"node:timers/promises\";\nimport { OutpostError, recordRecovery } from \"../domain/errors.ts\";\nimport { task } from \"../domain/workflow.ts\";\nimport { queueDefaultPollMs } from \"../domain/task-queue.constants.ts\";\nimport { queueNumber, queueString } from \"../domain/task-queue.ts\";\nimport type { Task } from \"../domain/workflow.types.ts\";\nimport type { QueuedTaskOptions } from \"./queued-task.types.ts\";\nimport type { QueueQuota } from \"../domain/task-queue.types.ts\";\n\nexport function queuedTask<T>(options: QueuedTaskOptions<T>): Task<T> {\n const {\n queue,\n handler,\n input,\n decode,\n deadline,\n pollMs = queueDefaultPollMs,\n ...definition\n } = options;\n queueString(handler);\n if (!queueNumber(pollMs))\n throw new Error(\"Queue poll interval must be positive\");\n if (deadline !== undefined) queueNumber(deadline);\n return task({\n ...definition,\n async perform(context) {\n context.signal.throwIfAborted();\n const key = taskIdempotencyKey(context.executionId, definition.key);\n const id = context.quota ? `${key}:quota:${context.attempt}` : key;\n let job = await queue.enqueue({\n id,\n ...(context.quota ? { idempotencyKey: key } : {}),\n handler,\n input: input(context),\n ...(deadline === undefined ? {} : { deadline }),\n });\n context.observation?.emit(\"workflow\", {\n kind: \"queue\",\n id,\n status: \"enqueued\",\n });\n try {\n while (job.status === \"pending\" || job.status === \"active\") {\n await delay(pollMs, undefined, { signal: context.signal });\n const latest = await queue.get(id);\n if (!latest) throw new Error(\"Queued workflow job disappeared\");\n job = latest;\n context.observation?.emit(\"workflow\", {\n kind: \"queue\",\n id,\n status: \"polled\",\n });\n }\n if (job.result?.usage) {\n if (context.reportUsageOnce)\n context.reportUsageOnce(`queue:${id}`, job.result.usage);\n else context.reportUsage(job.result.usage);\n }\n if (job.status !== \"done\" || !job.result)\n throw queuedFailure(job.result?.error, job.result?.quota, id);\n context.signal.throwIfAborted();\n const value = decode(job.result.value);\n context.observation?.emit(\"workflow\", {\n kind: \"queue\",\n id,\n status: \"completed\",\n });\n return value;\n } catch (error) {\n context.observation?.emit(\"workflow\", {\n kind: \"queue\",\n id,\n status: \"failed\",\n });\n if (context.signal.aborted) {\n const latest = await queue.get(id);\n if (latest) await queue.cancel(id, latest.fence);\n }\n throw error;\n }\n },\n });\n}\n\nfunction queuedFailure(\n message: string | undefined,\n quota: QueueQuota | undefined,\n job: string,\n): Error {\n if (!quota) return new Error(message ?? \"Queued workflow job cancelled\");\n const error = new OutpostError(\n \"quota\",\n message ?? \"Queued workflow job reached a usage limit\",\n {\n job,\n ...(quota.resetAt ? { resetAt: quota.resetAt } : {}),\n ...(quota.conversation ? { conversation: quota.conversation } : {}),\n },\n );\n if (quota.conversation)\n recordRecovery(error, { conversation: quota.conversation });\n return error;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const quotaResumeInstructions = "Your previous turn was interrupted by a usage or rate limit before it finished. Continue the same task from where you stopped. Inspect the workspace first: changes that were not committed may be missing.";
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export const quotaResumeInstructions = "Your previous turn was interrupted by a usage or rate limit before it finished. Continue the same task from where you stopped. Inspect the workspace first: changes that were not committed may be missing.";
|
|
2
|
+
//# sourceMappingURL=quota-resume.constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quota-resume.constants.js","sourceRoot":"","sources":["../../src/application/quota-resume.constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,uBAAuB,GAClC,6MAA6M,CAAC","sourcesContent":["export const quotaResumeInstructions =\n \"Your previous turn was interrupted by a usage or rate limit before it finished. Continue the same task from where you stopped. Inspect the workspace first: changes that were not committed may be missing.\";\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { TaskContext } from "../domain/workflow.types.ts";
|
|
2
|
+
import type { DispatchOptions } from "./execution.types.ts";
|
|
3
|
+
import type { SandboxOptions } from "./outpost.types.ts";
|
|
4
|
+
import type { QuotaResumePolicy } from "./quota-resume.types.ts";
|
|
5
|
+
export declare function quotaResumeBrief(tag?: string): string;
|
|
6
|
+
/** Continues the conversation interrupted by a quota pause when the dispatch allows it. */
|
|
7
|
+
export declare function quotaContinuation<O extends DispatchOptions<unknown>>(context: TaskContext, options: O, policy?: QuotaResumePolicy): O;
|
|
8
|
+
/** Starts an automatically integrated workspace from the interrupted branch. */
|
|
9
|
+
export declare function quotaWorkspace<T extends SandboxOptions & DispatchOptions<unknown>>(context: TaskContext, options: T): T;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { executionDefaults } from "./execution.constants.js";
|
|
2
|
+
import { quotaResumeInstructions } from "./quota-resume.constants.js";
|
|
3
|
+
export function quotaResumeBrief(tag) {
|
|
4
|
+
return tag === undefined
|
|
5
|
+
? quotaResumeInstructions
|
|
6
|
+
: `${quotaResumeInstructions}\nReturn the final answer inside <${tag}> and </${tag}>.`;
|
|
7
|
+
}
|
|
8
|
+
/** Continues the conversation interrupted by a quota pause when the dispatch allows it. */
|
|
9
|
+
export function quotaContinuation(context, options, policy = "continue") {
|
|
10
|
+
const conversation = context.quota?.conversation;
|
|
11
|
+
if (policy === "restart" ||
|
|
12
|
+
!conversation ||
|
|
13
|
+
options.continuation ||
|
|
14
|
+
options.agent?.resumable === false ||
|
|
15
|
+
(options.passes ?? executionDefaults.passes) !== 1)
|
|
16
|
+
return options;
|
|
17
|
+
return {
|
|
18
|
+
...options,
|
|
19
|
+
brief: { text: quotaResumeBrief(options.response?.tag) },
|
|
20
|
+
continuation: { id: conversation },
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/** Starts an automatically integrated workspace from the interrupted branch. */
|
|
24
|
+
export function quotaWorkspace(context, options) {
|
|
25
|
+
const branch = context.quota?.branch;
|
|
26
|
+
if (!branch || !options.continuation || options.workspace)
|
|
27
|
+
return options;
|
|
28
|
+
const integrated = options.branch?.mode === "integrate" ||
|
|
29
|
+
(!options.branch && options.sandboxProvider?.placement === "remote");
|
|
30
|
+
if (!integrated)
|
|
31
|
+
return options;
|
|
32
|
+
return { ...options, branch: { mode: "integrate", from: branch } };
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=quota-resume.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quota-resume.js","sourceRoot":"","sources":["../../src/application/quota-resume.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAG7D,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AAGtE,MAAM,UAAU,gBAAgB,CAAC,GAAY;IAC3C,OAAO,GAAG,KAAK,SAAS;QACtB,CAAC,CAAC,uBAAuB;QACzB,CAAC,CAAC,GAAG,uBAAuB,qCAAqC,GAAG,WAAW,GAAG,IAAI,CAAC;AAC3F,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,iBAAiB,CAC/B,OAAoB,EACpB,OAAU,EACV,SAA4B,UAAU;IAEtC,MAAM,YAAY,GAAG,OAAO,CAAC,KAAK,EAAE,YAAY,CAAC;IACjD,IACE,MAAM,KAAK,SAAS;QACpB,CAAC,YAAY;QACb,OAAO,CAAC,YAAY;QACpB,OAAO,CAAC,KAAK,EAAE,SAAS,KAAK,KAAK;QAClC,CAAC,OAAO,CAAC,MAAM,IAAI,iBAAiB,CAAC,MAAM,CAAC,KAAK,CAAC;QAElD,OAAO,OAAO,CAAC;IACjB,OAAO;QACL,GAAG,OAAO;QACV,KAAK,EAAE,EAAE,IAAI,EAAE,gBAAgB,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE;QACxD,YAAY,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE;KACnC,CAAC;AACJ,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,cAAc,CAE5B,OAAoB,EAAE,OAAU;IAChC,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC;IACrC,IAAI,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,YAAY,IAAI,OAAO,CAAC,SAAS;QAAE,OAAO,OAAO,CAAC;IAC1E,MAAM,UAAU,GACd,OAAO,CAAC,MAAM,EAAE,IAAI,KAAK,WAAW;QACpC,CAAC,CAAC,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,eAAe,EAAE,SAAS,KAAK,QAAQ,CAAC,CAAC;IACvE,IAAI,CAAC,UAAU;QAAE,OAAO,OAAO,CAAC;IAChC,OAAO,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC;AACrE,CAAC","sourcesContent":["import type { TaskContext } from \"../domain/workflow.types.ts\";\nimport { executionDefaults } from \"./execution.constants.ts\";\nimport type { DispatchOptions } from \"./execution.types.ts\";\nimport type { SandboxOptions } from \"./outpost.types.ts\";\nimport { quotaResumeInstructions } from \"./quota-resume.constants.ts\";\nimport type { QuotaResumePolicy } from \"./quota-resume.types.ts\";\n\nexport function quotaResumeBrief(tag?: string): string {\n return tag === undefined\n ? quotaResumeInstructions\n : `${quotaResumeInstructions}\\nReturn the final answer inside <${tag}> and </${tag}>.`;\n}\n\n/** Continues the conversation interrupted by a quota pause when the dispatch allows it. */\nexport function quotaContinuation<O extends DispatchOptions<unknown>>(\n context: TaskContext,\n options: O,\n policy: QuotaResumePolicy = \"continue\",\n): O {\n const conversation = context.quota?.conversation;\n if (\n policy === \"restart\" ||\n !conversation ||\n options.continuation ||\n options.agent?.resumable === false ||\n (options.passes ?? executionDefaults.passes) !== 1\n )\n return options;\n return {\n ...options,\n brief: { text: quotaResumeBrief(options.response?.tag) },\n continuation: { id: conversation },\n };\n}\n\n/** Starts an automatically integrated workspace from the interrupted branch. */\nexport function quotaWorkspace<\n T extends SandboxOptions & DispatchOptions<unknown>,\n>(context: TaskContext, options: T): T {\n const branch = context.quota?.branch;\n if (!branch || !options.continuation || options.workspace) return options;\n const integrated =\n options.branch?.mode === \"integrate\" ||\n (!options.branch && options.sandboxProvider?.placement === \"remote\");\n if (!integrated) return options;\n return { ...options, branch: { mode: \"integrate\", from: branch } };\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type QuotaResumePolicy = "continue" | "restart";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quota-resume.types.js","sourceRoot":"","sources":["../../src/application/quota-resume.types.ts"],"names":[],"mappings":"","sourcesContent":["export type QuotaResumePolicy = \"continue\" | \"restart\";\n"]}
|
|
@@ -5,7 +5,7 @@ export function retentionPolicy(value) {
|
|
|
5
5
|
invariant("scopes" in value &&
|
|
6
6
|
Array.isArray(value.scopes) &&
|
|
7
7
|
value.scopes.length > 0 &&
|
|
8
|
-
value.scopes.every((scope) =>
|
|
8
|
+
value.scopes.every((scope) => ["clean-workspaces", "closed-logs", "task-cache"].includes(String(scope))), "Retention scopes must contain clean-workspaces, closed-logs or task-cache");
|
|
9
9
|
invariant("minAgeMs" in value &&
|
|
10
10
|
typeof value.minAgeMs === "number" &&
|
|
11
11
|
Number.isSafeInteger(value.minAgeMs) &&
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recovery-retention-policy.js","sourceRoot":"","sources":["../../src/application/recovery-retention-policy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGhD,MAAM,UAAU,eAAe,CAC7B,KAAc;IAEd,SAAS,CACP,CAAC,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EACpC,oCAAoC,CACrC,CAAC;IACF,SAAS,CACP,SAAS,IAAI,KAAK,IAAI,KAAK,CAAC,OAAO,KAAK,CAAC,EACzC,oCAAoC,CACrC,CAAC;IACF,SAAS,CACP,QAAQ,IAAI,KAAK;QACf,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC;QAC3B,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC;QACvB,KAAK,CAAC,MAAM,CAAC,KAAK,
|
|
1
|
+
{"version":3,"file":"recovery-retention-policy.js","sourceRoot":"","sources":["../../src/application/recovery-retention-policy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGhD,MAAM,UAAU,eAAe,CAC7B,KAAc;IAEd,SAAS,CACP,CAAC,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EACpC,oCAAoC,CACrC,CAAC;IACF,SAAS,CACP,SAAS,IAAI,KAAK,IAAI,KAAK,CAAC,OAAO,KAAK,CAAC,EACzC,oCAAoC,CACrC,CAAC;IACF,SAAS,CACP,QAAQ,IAAI,KAAK;QACf,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC;QAC3B,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC;QACvB,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE,CACpC,CAAC,kBAAkB,EAAE,aAAa,EAAE,YAAY,CAAC,CAAC,QAAQ,CACxD,MAAM,CAAC,KAAK,CAAC,CACd,CACF,EACH,2EAA2E,CAC5E,CAAC;IACF,SAAS,CACP,UAAU,IAAI,KAAK;QACjB,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ;QAClC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,QAAQ,CAAC;QACpC,KAAK,CAAC,QAAQ,IAAI,CAAC,EACrB,wCAAwC,CACzC,CAAC;IACF,KAAK,MAAM,GAAG,IAAI,CAAC,UAAU,EAAE,eAAe,CAAU,EAAE,CAAC;QACzD,IAAI,CAAC,CAAC,GAAG,IAAI,KAAK,CAAC;YAAE,SAAS;QAC9B,MAAM,KAAK,GAAY,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,KAAK,CAAC;QAC1E,SAAS,CACP,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,EACtE,GAAG,GAAG,gCAAgC,CACvC,CAAC;IACJ,CAAC;IACD,SAAS,CACP,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAC/B,CAAC,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC,QAAQ,CACrE,GAAG,CACJ,CACF,EACD,oCAAoC,CACrC,CAAC;AACJ,CAAC","sourcesContent":["import { invariant } from \"../domain/errors.ts\";\nimport type { RecoveryRetentionPolicy } from \"./recovery-retention.types.ts\";\n\nexport function retentionPolicy(\n value: unknown,\n): asserts value is RecoveryRetentionPolicy {\n invariant(\n !!value && typeof value === \"object\",\n \"Retention policy must be an object\",\n );\n invariant(\n \"version\" in value && value.version === 1,\n \"Retention policy version must be 1\",\n );\n invariant(\n \"scopes\" in value &&\n Array.isArray(value.scopes) &&\n value.scopes.length > 0 &&\n value.scopes.every((scope: unknown) =>\n [\"clean-workspaces\", \"closed-logs\", \"task-cache\"].includes(\n String(scope),\n ),\n ),\n \"Retention scopes must contain clean-workspaces, closed-logs or task-cache\",\n );\n invariant(\n \"minAgeMs\" in value &&\n typeof value.minAgeMs === \"number\" &&\n Number.isSafeInteger(value.minAgeMs) &&\n value.minAgeMs >= 0,\n \"minAgeMs must be a nonnegative integer\",\n );\n for (const key of [\"maxBytes\", \"maxWorkspaces\"] as const) {\n if (!(key in value)) continue;\n const limit: unknown = Object.getOwnPropertyDescriptor(value, key)?.value;\n invariant(\n typeof limit === \"number\" && Number.isSafeInteger(limit) && limit >= 0,\n `${key} must be a nonnegative integer`,\n );\n }\n invariant(\n Object.keys(value).every((key) =>\n [\"version\", \"scopes\", \"minAgeMs\", \"maxBytes\", \"maxWorkspaces\"].includes(\n key,\n ),\n ),\n \"Unsupported retention policy field\",\n );\n}\n"]}
|
|
@@ -101,14 +101,14 @@ async function planRetentionOperation(options) {
|
|
|
101
101
|
transporter: repositoryTransport(inspection.repository),
|
|
102
102
|
policy: {
|
|
103
103
|
version: 1,
|
|
104
|
-
scopes: policy.scopes.filter((scope) => scope
|
|
104
|
+
scopes: policy.scopes.filter((scope) => scope !== "clean-workspaces"),
|
|
105
105
|
minAgeMs: policy.minAgeMs,
|
|
106
106
|
},
|
|
107
107
|
...(options.maxEntries === undefined
|
|
108
108
|
? {}
|
|
109
109
|
: { maxEntries: options.maxEntries }),
|
|
110
110
|
});
|
|
111
|
-
for (const entry of logs.entries.filter((entry) => entry.category
|
|
111
|
+
for (const entry of logs.entries.filter((entry) => ["logs", "task-cache"].includes(entry.category))) {
|
|
112
112
|
let bytes = 0;
|
|
113
113
|
for (const object of entry.objects ?? []) {
|
|
114
114
|
try {
|
|
@@ -212,7 +212,7 @@ async function pruneRetentionOperation(plan, options) {
|
|
|
212
212
|
source: "transport",
|
|
213
213
|
policy: {
|
|
214
214
|
version: 1,
|
|
215
|
-
scopes:
|
|
215
|
+
scopes: plan.policy.scopes.filter((scope) => scope !== "clean-workspaces"),
|
|
216
216
|
minAgeMs: plan.policy.minAgeMs,
|
|
217
217
|
},
|
|
218
218
|
entries: [candidate],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recovery-retention.js","sourceRoot":"","sources":["../../src/application/recovery-retention.ts"],"names":[],"mappings":"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","sourcesContent":["import { observedOperation } from \"../domain/observed-operation.ts\";\nimport type { ObservationHub } from \"../domain/observation.types.ts\";\nimport type { TransportStoreOptions } from \"../domain/transport.types.ts\";\nimport {\n planTransportRetention,\n pruneTransportRetention,\n} from \"./transport-retention.ts\";\nimport { lstat } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { repositoryTransport } from \"../infrastructure/repository-transport.ts\";\nimport { repositoryStorageDirectory } from \"../infrastructure/repository-transport.constants.ts\";\nimport { OutpostError, invariant } from \"../domain/errors.ts\";\nimport { git } from \"../infrastructure/git/command.ts\";\nimport { lock, lockPath } from \"../infrastructure/git/lock.ts\";\nimport type { StorageEntry } from \"../infrastructure/storage-inventory.types.ts\";\nimport type { RecoveryInspection } from \"./recovery-inspection.types.ts\";\nimport { inspectRecovery } from \"./recovery-inspection.ts\";\nimport { retentionPolicy } from \"./recovery-retention-policy.ts\";\nimport type {\n RecoveryPruneResult,\n RecoveryRetainedEntry,\n RecoveryQuotaOptions,\n RecoveryRetentionEntry,\n RecoveryRetentionOptions,\n RecoveryRetentionPlan,\n} from \"./recovery-retention.types.ts\";\n\nasync function workspaceReason(\n entry: StorageEntry,\n inspection: RecoveryInspection,\n): Promise<string> {\n if (inspection.resources?.complete === false)\n return \"RESOURCE_ACTIVITY_UNKNOWN\";\n if (\n inspection.resources?.entries.some(\n (resource) => resource.record?.workspace === entry.path,\n )\n )\n return \"RESOURCE_ACTIVITY_RECORDED\";\n const workspace = inspection.git?.workspaces.find(\n (value) => value.path === entry.path,\n );\n if (!workspace || workspace.state !== \"registered\")\n return \"WORKSPACE_UNREGISTERED_OR_UNKNOWN\";\n if (workspace.branch === null) return \"DETACHED_WORKSPACE\";\n if (workspace.dirty) return \"DIRTY_WORKSPACE\";\n if (workspace.locked) return \"GIT_LOCKED_WORKSPACE\";\n try {\n await lstat(lockPath(inspection.repository, workspace.branch));\n return \"OPERATION_LOCK_PRESENT\";\n } catch (error) {\n if (!(\n error &&\n typeof error === \"object\" &&\n \"code\" in error &&\n error.code === \"ENOENT\"\n ))\n return \"OWNERSHIP_UNKNOWN\";\n }\n try {\n const ignored = await git(entry.path, [\n \"ls-files\",\n \"--others\",\n \"--ignored\",\n \"--exclude-standard\",\n \"-z\",\n ]);\n if (ignored.length) return \"IGNORED_FILES\";\n } catch {\n return \"WORKSPACE_STATE_UNKNOWN\";\n }\n return \"ELIGIBLE\";\n}\n\nasync function planRetentionOperation(\n options: RecoveryRetentionOptions,\n): Promise<RecoveryRetentionPlan> {\n if (options.transporter) return planTransportRetention(options);\n retentionPolicy(options.policy);\n const policy = structuredClone(options.policy);\n const inspection = await inspectRecovery({\n ...options,\n git: true,\n locks: true,\n resources: true,\n });\n const entries: RecoveryRetentionEntry[] = [];\n const inspectedAt = new Date().toISOString();\n let complete = inspection.complete && inspection.git?.complete !== false;\n for (const category of inspection.categories) {\n for (const entry of category.entries) {\n let reason = \"RECOVERY_DATA_PROTECTED\";\n if (category.name === \"locks\") reason = \"OWNERSHIP_RECORD_PROTECTED\";\n if (category.name === \"logs\") reason = \"LOG_ACTIVITY_OR_CONTENT_UNKNOWN\";\n if (category.name === \"workspaces\") reason = \"SCOPE_NOT_SELECTED\";\n if (\n category.name === \"workspaces\" &&\n policy.scopes.includes(\"clean-workspaces\")\n )\n reason = await workspaceReason(entry, inspection);\n if (\n reason === \"ELIGIBLE\" &&\n (!entry.modifiedAt ||\n Date.parse(inspectedAt) - Date.parse(entry.modifiedAt) <\n policy.minAgeMs)\n )\n reason = \"RETENTION_AGE\";\n if (reason === \"ELIGIBLE\" && (!complete || !entry.complete))\n reason = \"INCOMPLETE_INVENTORY\";\n const workspace = inspection.git?.workspaces.find(\n (value) => value.path === entry.path,\n );\n entries.push({\n path: entry.path,\n category: category.name,\n bytes: entry.bytes,\n eligible: reason === \"ELIGIBLE\",\n reason,\n ...(entry.modifiedAt ? { modifiedAt: entry.modifiedAt } : {}),\n ...(workspace?.state === \"registered\" && workspace.branch\n ? { branch: workspace.branch, head: workspace.head }\n : {}),\n });\n }\n }\n const logs = await planTransportRetention({\n transporter: repositoryTransport(inspection.repository),\n policy: {\n version: 1,\n scopes: policy.scopes.filter((scope) => scope === \"closed-logs\"),\n minAgeMs: policy.minAgeMs,\n },\n ...(options.maxEntries === undefined\n ? {}\n : { maxEntries: options.maxEntries }),\n });\n for (const entry of logs.entries.filter(\n (entry) => entry.category === \"logs\",\n )) {\n let bytes = 0;\n for (const object of entry.objects ?? []) {\n try {\n bytes += (\n await lstat(\n join(\n inspection.repository,\n \".outpost\",\n repositoryStorageDirectory,\n \"objects\",\n `${object.key}.object`,\n ),\n )\n ).size;\n } catch {\n complete = false;\n }\n }\n const container = entries.findIndex(\n (value) =>\n value.category === \"storage\" &&\n value.path ===\n join(\n inspection.repository,\n \".outpost\",\n repositoryStorageDirectory,\n \"objects\",\n ),\n );\n const stored = entries[container];\n if (!stored || stored.bytes < bytes) complete = false;\n if (stored)\n entries[container] = {\n ...stored,\n bytes: Math.max(0, stored.bytes - bytes),\n };\n entries.push({ ...entry, bytes, eligible: entry.eligible && complete });\n }\n complete = complete && logs.complete;\n if (!complete) {\n for (let index = 0; index < entries.length; index++) {\n const entry = entries[index]!;\n if (entry.eligible)\n entries[index] = {\n ...entry,\n eligible: false,\n reason: \"INCOMPLETE_INVENTORY\",\n };\n }\n }\n const projectedBytes =\n inspection.usage.bytes -\n entries\n .filter((entry) => entry.eligible)\n .reduce((sum, entry) => sum + entry.bytes, 0);\n const workspaces = entries.filter(\n (entry) => entry.category === \"workspaces\" && !entry.eligible,\n ).length;\n const exceeded =\n (policy.maxBytes !== undefined && projectedBytes > policy.maxBytes) ||\n (policy.maxWorkspaces !== undefined && workspaces > policy.maxWorkspaces);\n return {\n repository: inspection.repository,\n policy,\n inspectedAt,\n inspection,\n entries,\n complete,\n usageBytes: inspection.usage.bytes,\n projectedBytes,\n quota: !complete ? \"unknown\" : exceeded ? \"exceeded\" : \"within\",\n };\n}\n\nasync function pruneRetentionOperation(\n plan: RecoveryRetentionPlan,\n options?: TransportStoreOptions,\n): Promise<RecoveryPruneResult> {\n if (plan.source === \"transport\") {\n invariant(options, \"Transport retention requires its transporter\");\n return pruneTransportRetention(plan, options);\n }\n invariant(!options, \"Local retention does not accept a transporter\");\n retentionPolicy(plan.policy);\n invariant(plan.complete, \"Cannot prune an incomplete retention plan\");\n const removed: string[] = [];\n const retained: RecoveryRetainedEntry[] = [];\n for (const candidate of plan.entries.filter((entry) => entry.eligible)) {\n const fresh = await planRecoveryRetention({\n repository: plan.repository,\n policy: plan.policy,\n });\n const entry = fresh.entries.find((value) => value.path === candidate.path);\n if (\n !entry?.eligible ||\n entry.head !== candidate.head ||\n entry.branch !== candidate.branch ||\n entry.bytes !== candidate.bytes ||\n entry.modifiedAt !== candidate.modifiedAt\n ) {\n retained.push({ path: candidate.path, reason: \"PLAN_CHANGED\" });\n continue;\n }\n try {\n if (entry.category === \"workspaces\" && entry.branch) {\n const release = await lock(plan.repository, entry.branch);\n try {\n const status = await git(entry.path, [\n \"status\",\n \"--porcelain=v1\",\n \"--ignored\",\n \"--untracked-files=all\",\n ]);\n invariant(!status.length, \"Workspace changed before pruning\");\n invariant(\n (await git(entry.path, [\"rev-parse\", \"HEAD\"])).trim() ===\n entry.head,\n \"Workspace HEAD changed before pruning\",\n );\n invariant(\n (\n await git(entry.path, [\"symbolic-ref\", \"--short\", \"HEAD\"])\n ).trim() === entry.branch,\n \"Workspace branch changed before pruning\",\n );\n await git(plan.repository, [\"worktree\", \"remove\", entry.path]);\n } finally {\n await release();\n }\n } else {\n const result = await pruneTransportRetention(\n {\n ...plan,\n source: \"transport\",\n policy: {\n version: 1,\n scopes: [\"closed-logs\"],\n minAgeMs: plan.policy.minAgeMs,\n },\n entries: [candidate],\n },\n { transporter: repositoryTransport(plan.repository) },\n );\n if (!result.removed.includes(entry.path)) {\n retained.push(...result.retained);\n continue;\n }\n }\n removed.push(entry.path);\n } catch {\n retained.push({\n path: candidate.path,\n reason: \"REVALIDATION_OR_REMOVAL_FAILED\",\n });\n }\n }\n return {\n removed,\n retained,\n after: await planRecoveryRetention({\n repository: plan.repository,\n policy: plan.policy,\n }),\n };\n}\n\nexport async function assertRecoveryQuota(\n options: RecoveryQuotaOptions,\n): Promise<void> {\n invariant(\n Number.isSafeInteger(options.maxBytes) && options.maxBytes >= 0,\n \"maxBytes must be a nonnegative integer\",\n );\n const reserve = options.reserveBytes ?? 0;\n invariant(\n Number.isSafeInteger(reserve) && reserve >= 0,\n \"reserveBytes must be a nonnegative integer\",\n );\n const inspection = await inspectRecovery(options);\n if (\n !inspection.complete ||\n inspection.usage.bytes + reserve > options.maxBytes\n )\n throw new OutpostError(\n \"workspace\",\n \"Outpost storage quota admission refused\",\n {\n usageBytes: inspection.usage.bytes,\n reserveBytes: reserve,\n maxBytes: options.maxBytes,\n complete: inspection.complete,\n },\n );\n}\n\nexport function planRecoveryRetention(\n options: RecoveryRetentionOptions,\n observation?: ObservationHub,\n): Promise<RecoveryRetentionPlan> {\n return observedOperation(observation, \"recovery\", \"retention.plan\", () =>\n planRetentionOperation(options),\n );\n}\n\nexport function pruneRecoveryRetention(\n plan: RecoveryRetentionPlan,\n options?: TransportStoreOptions,\n observation?: ObservationHub,\n): Promise<RecoveryPruneResult> {\n return observedOperation(observation, \"recovery\", \"retention.prune\", () =>\n pruneRetentionOperation(plan, options),\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"recovery-retention.js","sourceRoot":"","sources":["../../src/application/recovery-retention.ts"],"names":[],"mappings":"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","sourcesContent":["import { observedOperation } from \"../domain/observed-operation.ts\";\nimport type { ObservationHub } from \"../domain/observation.types.ts\";\nimport type { TransportStoreOptions } from \"../domain/transport.types.ts\";\nimport {\n planTransportRetention,\n pruneTransportRetention,\n} from \"./transport-retention.ts\";\nimport { lstat } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { repositoryTransport } from \"../infrastructure/repository-transport.ts\";\nimport { repositoryStorageDirectory } from \"../infrastructure/repository-transport.constants.ts\";\nimport { OutpostError, invariant } from \"../domain/errors.ts\";\nimport { git } from \"../infrastructure/git/command.ts\";\nimport { lock, lockPath } from \"../infrastructure/git/lock.ts\";\nimport type { StorageEntry } from \"../infrastructure/storage-inventory.types.ts\";\nimport type { RecoveryInspection } from \"./recovery-inspection.types.ts\";\nimport { inspectRecovery } from \"./recovery-inspection.ts\";\nimport { retentionPolicy } from \"./recovery-retention-policy.ts\";\nimport type {\n RecoveryPruneResult,\n RecoveryRetainedEntry,\n RecoveryQuotaOptions,\n RecoveryRetentionEntry,\n RecoveryRetentionOptions,\n RecoveryRetentionPlan,\n} from \"./recovery-retention.types.ts\";\n\nasync function workspaceReason(\n entry: StorageEntry,\n inspection: RecoveryInspection,\n): Promise<string> {\n if (inspection.resources?.complete === false)\n return \"RESOURCE_ACTIVITY_UNKNOWN\";\n if (\n inspection.resources?.entries.some(\n (resource) => resource.record?.workspace === entry.path,\n )\n )\n return \"RESOURCE_ACTIVITY_RECORDED\";\n const workspace = inspection.git?.workspaces.find(\n (value) => value.path === entry.path,\n );\n if (!workspace || workspace.state !== \"registered\")\n return \"WORKSPACE_UNREGISTERED_OR_UNKNOWN\";\n if (workspace.branch === null) return \"DETACHED_WORKSPACE\";\n if (workspace.dirty) return \"DIRTY_WORKSPACE\";\n if (workspace.locked) return \"GIT_LOCKED_WORKSPACE\";\n try {\n await lstat(lockPath(inspection.repository, workspace.branch));\n return \"OPERATION_LOCK_PRESENT\";\n } catch (error) {\n if (!(\n error &&\n typeof error === \"object\" &&\n \"code\" in error &&\n error.code === \"ENOENT\"\n ))\n return \"OWNERSHIP_UNKNOWN\";\n }\n try {\n const ignored = await git(entry.path, [\n \"ls-files\",\n \"--others\",\n \"--ignored\",\n \"--exclude-standard\",\n \"-z\",\n ]);\n if (ignored.length) return \"IGNORED_FILES\";\n } catch {\n return \"WORKSPACE_STATE_UNKNOWN\";\n }\n return \"ELIGIBLE\";\n}\n\nasync function planRetentionOperation(\n options: RecoveryRetentionOptions,\n): Promise<RecoveryRetentionPlan> {\n if (options.transporter) return planTransportRetention(options);\n retentionPolicy(options.policy);\n const policy = structuredClone(options.policy);\n const inspection = await inspectRecovery({\n ...options,\n git: true,\n locks: true,\n resources: true,\n });\n const entries: RecoveryRetentionEntry[] = [];\n const inspectedAt = new Date().toISOString();\n let complete = inspection.complete && inspection.git?.complete !== false;\n for (const category of inspection.categories) {\n for (const entry of category.entries) {\n let reason = \"RECOVERY_DATA_PROTECTED\";\n if (category.name === \"locks\") reason = \"OWNERSHIP_RECORD_PROTECTED\";\n if (category.name === \"logs\") reason = \"LOG_ACTIVITY_OR_CONTENT_UNKNOWN\";\n if (category.name === \"workspaces\") reason = \"SCOPE_NOT_SELECTED\";\n if (\n category.name === \"workspaces\" &&\n policy.scopes.includes(\"clean-workspaces\")\n )\n reason = await workspaceReason(entry, inspection);\n if (\n reason === \"ELIGIBLE\" &&\n (!entry.modifiedAt ||\n Date.parse(inspectedAt) - Date.parse(entry.modifiedAt) <\n policy.minAgeMs)\n )\n reason = \"RETENTION_AGE\";\n if (reason === \"ELIGIBLE\" && (!complete || !entry.complete))\n reason = \"INCOMPLETE_INVENTORY\";\n const workspace = inspection.git?.workspaces.find(\n (value) => value.path === entry.path,\n );\n entries.push({\n path: entry.path,\n category: category.name,\n bytes: entry.bytes,\n eligible: reason === \"ELIGIBLE\",\n reason,\n ...(entry.modifiedAt ? { modifiedAt: entry.modifiedAt } : {}),\n ...(workspace?.state === \"registered\" && workspace.branch\n ? { branch: workspace.branch, head: workspace.head }\n : {}),\n });\n }\n }\n const logs = await planTransportRetention({\n transporter: repositoryTransport(inspection.repository),\n policy: {\n version: 1,\n scopes: policy.scopes.filter((scope) => scope !== \"clean-workspaces\"),\n minAgeMs: policy.minAgeMs,\n },\n ...(options.maxEntries === undefined\n ? {}\n : { maxEntries: options.maxEntries }),\n });\n for (const entry of logs.entries.filter((entry) =>\n [\"logs\", \"task-cache\"].includes(entry.category),\n )) {\n let bytes = 0;\n for (const object of entry.objects ?? []) {\n try {\n bytes += (\n await lstat(\n join(\n inspection.repository,\n \".outpost\",\n repositoryStorageDirectory,\n \"objects\",\n `${object.key}.object`,\n ),\n )\n ).size;\n } catch {\n complete = false;\n }\n }\n const container = entries.findIndex(\n (value) =>\n value.category === \"storage\" &&\n value.path ===\n join(\n inspection.repository,\n \".outpost\",\n repositoryStorageDirectory,\n \"objects\",\n ),\n );\n const stored = entries[container];\n if (!stored || stored.bytes < bytes) complete = false;\n if (stored)\n entries[container] = {\n ...stored,\n bytes: Math.max(0, stored.bytes - bytes),\n };\n entries.push({ ...entry, bytes, eligible: entry.eligible && complete });\n }\n complete = complete && logs.complete;\n if (!complete) {\n for (let index = 0; index < entries.length; index++) {\n const entry = entries[index]!;\n if (entry.eligible)\n entries[index] = {\n ...entry,\n eligible: false,\n reason: \"INCOMPLETE_INVENTORY\",\n };\n }\n }\n const projectedBytes =\n inspection.usage.bytes -\n entries\n .filter((entry) => entry.eligible)\n .reduce((sum, entry) => sum + entry.bytes, 0);\n const workspaces = entries.filter(\n (entry) => entry.category === \"workspaces\" && !entry.eligible,\n ).length;\n const exceeded =\n (policy.maxBytes !== undefined && projectedBytes > policy.maxBytes) ||\n (policy.maxWorkspaces !== undefined && workspaces > policy.maxWorkspaces);\n return {\n repository: inspection.repository,\n policy,\n inspectedAt,\n inspection,\n entries,\n complete,\n usageBytes: inspection.usage.bytes,\n projectedBytes,\n quota: !complete ? \"unknown\" : exceeded ? \"exceeded\" : \"within\",\n };\n}\n\nasync function pruneRetentionOperation(\n plan: RecoveryRetentionPlan,\n options?: TransportStoreOptions,\n): Promise<RecoveryPruneResult> {\n if (plan.source === \"transport\") {\n invariant(options, \"Transport retention requires its transporter\");\n return pruneTransportRetention(plan, options);\n }\n invariant(!options, \"Local retention does not accept a transporter\");\n retentionPolicy(plan.policy);\n invariant(plan.complete, \"Cannot prune an incomplete retention plan\");\n const removed: string[] = [];\n const retained: RecoveryRetainedEntry[] = [];\n for (const candidate of plan.entries.filter((entry) => entry.eligible)) {\n const fresh = await planRecoveryRetention({\n repository: plan.repository,\n policy: plan.policy,\n });\n const entry = fresh.entries.find((value) => value.path === candidate.path);\n if (\n !entry?.eligible ||\n entry.head !== candidate.head ||\n entry.branch !== candidate.branch ||\n entry.bytes !== candidate.bytes ||\n entry.modifiedAt !== candidate.modifiedAt\n ) {\n retained.push({ path: candidate.path, reason: \"PLAN_CHANGED\" });\n continue;\n }\n try {\n if (entry.category === \"workspaces\" && entry.branch) {\n const release = await lock(plan.repository, entry.branch);\n try {\n const status = await git(entry.path, [\n \"status\",\n \"--porcelain=v1\",\n \"--ignored\",\n \"--untracked-files=all\",\n ]);\n invariant(!status.length, \"Workspace changed before pruning\");\n invariant(\n (await git(entry.path, [\"rev-parse\", \"HEAD\"])).trim() ===\n entry.head,\n \"Workspace HEAD changed before pruning\",\n );\n invariant(\n (\n await git(entry.path, [\"symbolic-ref\", \"--short\", \"HEAD\"])\n ).trim() === entry.branch,\n \"Workspace branch changed before pruning\",\n );\n await git(plan.repository, [\"worktree\", \"remove\", entry.path]);\n } finally {\n await release();\n }\n } else {\n const result = await pruneTransportRetention(\n {\n ...plan,\n source: \"transport\",\n policy: {\n version: 1,\n scopes: plan.policy.scopes.filter(\n (scope) => scope !== \"clean-workspaces\",\n ),\n minAgeMs: plan.policy.minAgeMs,\n },\n entries: [candidate],\n },\n { transporter: repositoryTransport(plan.repository) },\n );\n if (!result.removed.includes(entry.path)) {\n retained.push(...result.retained);\n continue;\n }\n }\n removed.push(entry.path);\n } catch {\n retained.push({\n path: candidate.path,\n reason: \"REVALIDATION_OR_REMOVAL_FAILED\",\n });\n }\n }\n return {\n removed,\n retained,\n after: await planRecoveryRetention({\n repository: plan.repository,\n policy: plan.policy,\n }),\n };\n}\n\nexport async function assertRecoveryQuota(\n options: RecoveryQuotaOptions,\n): Promise<void> {\n invariant(\n Number.isSafeInteger(options.maxBytes) && options.maxBytes >= 0,\n \"maxBytes must be a nonnegative integer\",\n );\n const reserve = options.reserveBytes ?? 0;\n invariant(\n Number.isSafeInteger(reserve) && reserve >= 0,\n \"reserveBytes must be a nonnegative integer\",\n );\n const inspection = await inspectRecovery(options);\n if (\n !inspection.complete ||\n inspection.usage.bytes + reserve > options.maxBytes\n )\n throw new OutpostError(\n \"workspace\",\n \"Outpost storage quota admission refused\",\n {\n usageBytes: inspection.usage.bytes,\n reserveBytes: reserve,\n maxBytes: options.maxBytes,\n complete: inspection.complete,\n },\n );\n}\n\nexport function planRecoveryRetention(\n options: RecoveryRetentionOptions,\n observation?: ObservationHub,\n): Promise<RecoveryRetentionPlan> {\n return observedOperation(observation, \"recovery\", \"retention.plan\", () =>\n planRetentionOperation(options),\n );\n}\n\nexport function pruneRecoveryRetention(\n plan: RecoveryRetentionPlan,\n options?: TransportStoreOptions,\n observation?: ObservationHub,\n): Promise<RecoveryPruneResult> {\n return observedOperation(observation, \"recovery\", \"retention.prune\", () =>\n pruneRetentionOperation(plan, options),\n );\n}\n"]}
|
|
@@ -2,7 +2,7 @@ import type { Transport, TransportEntry } from "../domain/transport.types.ts";
|
|
|
2
2
|
import type { RecoveryInspection } from "./recovery-inspection.types.ts";
|
|
3
3
|
export interface RecoveryRetentionPolicy {
|
|
4
4
|
readonly version: 1;
|
|
5
|
-
readonly scopes: readonly ("clean-workspaces" | "closed-logs")[];
|
|
5
|
+
readonly scopes: readonly ("clean-workspaces" | "closed-logs" | "task-cache")[];
|
|
6
6
|
readonly minAgeMs: number;
|
|
7
7
|
readonly maxBytes?: number;
|
|
8
8
|
readonly maxWorkspaces?: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recovery-retention.types.js","sourceRoot":"","sources":["../../src/application/recovery-retention.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Transport, TransportEntry } from \"../domain/transport.types.ts\";\nimport type { RecoveryInspection } from \"./recovery-inspection.types.ts\";\nexport interface RecoveryRetentionPolicy {\n readonly version: 1;\n readonly scopes: readonly (\"clean-workspaces\" | \"closed-logs\")[];\n readonly minAgeMs: number;\n readonly maxBytes?: number;\n readonly maxWorkspaces?: number;\n}\nexport interface RecoveryRetentionOptions {\n readonly transporter?: Transport;\n readonly repository?: string;\n readonly policy: RecoveryRetentionPolicy;\n readonly maxEntries?: number;\n}\nexport interface RecoveryRetentionEntry {\n readonly revision?: string;\n readonly objects?: readonly TransportEntry[];\n readonly path: string;\n readonly category: string;\n readonly bytes: number;\n readonly eligible: boolean;\n readonly reason: string;\n readonly branch?: string;\n readonly head?: string;\n readonly modifiedAt?: string;\n}\nexport interface RecoveryRetentionPlan {\n readonly source?: \"transport\";\n readonly repository: string;\n readonly policy: RecoveryRetentionPolicy;\n readonly inspectedAt: string;\n readonly inspection: RecoveryInspection;\n readonly entries: readonly RecoveryRetentionEntry[];\n readonly complete: boolean;\n readonly usageBytes: number;\n readonly projectedBytes: number;\n readonly quota: \"within\" | \"exceeded\" | \"unknown\";\n}\nexport interface RecoveryRetainedEntry {\n readonly path: string;\n readonly reason: string;\n}\nexport interface RecoveryPruneResult {\n readonly removed: readonly string[];\n readonly retained: readonly {\n readonly path: string;\n readonly reason: string;\n }[];\n readonly after: RecoveryRetentionPlan;\n}\nexport interface RecoveryQuotaOptions {\n readonly transporter?: Transport;\n readonly repository?: string;\n readonly maxBytes: number;\n readonly reserveBytes?: number;\n readonly maxEntries?: number;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"recovery-retention.types.js","sourceRoot":"","sources":["../../src/application/recovery-retention.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Transport, TransportEntry } from \"../domain/transport.types.ts\";\nimport type { RecoveryInspection } from \"./recovery-inspection.types.ts\";\nexport interface RecoveryRetentionPolicy {\n readonly version: 1;\n readonly scopes: readonly (\n \"clean-workspaces\" | \"closed-logs\" | \"task-cache\"\n )[];\n readonly minAgeMs: number;\n readonly maxBytes?: number;\n readonly maxWorkspaces?: number;\n}\nexport interface RecoveryRetentionOptions {\n readonly transporter?: Transport;\n readonly repository?: string;\n readonly policy: RecoveryRetentionPolicy;\n readonly maxEntries?: number;\n}\nexport interface RecoveryRetentionEntry {\n readonly revision?: string;\n readonly objects?: readonly TransportEntry[];\n readonly path: string;\n readonly category: string;\n readonly bytes: number;\n readonly eligible: boolean;\n readonly reason: string;\n readonly branch?: string;\n readonly head?: string;\n readonly modifiedAt?: string;\n}\nexport interface RecoveryRetentionPlan {\n readonly source?: \"transport\";\n readonly repository: string;\n readonly policy: RecoveryRetentionPolicy;\n readonly inspectedAt: string;\n readonly inspection: RecoveryInspection;\n readonly entries: readonly RecoveryRetentionEntry[];\n readonly complete: boolean;\n readonly usageBytes: number;\n readonly projectedBytes: number;\n readonly quota: \"within\" | \"exceeded\" | \"unknown\";\n}\nexport interface RecoveryRetainedEntry {\n readonly path: string;\n readonly reason: string;\n}\nexport interface RecoveryPruneResult {\n readonly removed: readonly string[];\n readonly retained: readonly {\n readonly path: string;\n readonly reason: string;\n }[];\n readonly after: RecoveryRetentionPlan;\n}\nexport interface RecoveryQuotaOptions {\n readonly transporter?: Transport;\n readonly repository?: string;\n readonly maxBytes: number;\n readonly reserveBytes?: number;\n readonly maxEntries?: number;\n}\n"]}
|
|
@@ -16,7 +16,8 @@ export async function seedRemote(workspace, lease, options = {}) {
|
|
|
16
16
|
options.signal?.throwIfAborted();
|
|
17
17
|
const recovery = join(workspace.repository, ".outpost", "recovery", randomUUID());
|
|
18
18
|
await mkdir(recovery, { recursive: true });
|
|
19
|
-
|
|
19
|
+
// The root's parent may be read-only for the sandbox user; its own .git is writable.
|
|
20
|
+
const remoteBundle = posix.join(lease.root.replaceAll("\\", "/"), ".git", `outpost-${randomUUID()}.bundle`);
|
|
20
21
|
let initializing = true, failed = false;
|
|
21
22
|
const run = async (args) => {
|
|
22
23
|
let result;
|