@themoltnet/agent-daemon 0.6.2 → 0.8.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/README.md +1 -1
- package/dist/main.js +910 -257
- package/package.json +6 -6
package/dist/main.js
CHANGED
|
@@ -12,6 +12,7 @@ import { execFile, execFileSync } from "node:child_process";
|
|
|
12
12
|
import { DefaultResourceLoader, SessionManager, createAgentSession, createBashToolDefinition, createEditToolDefinition, createReadToolDefinition, createSyntheticSourceInfo, createWriteToolDefinition, defineTool, parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
|
13
13
|
import { Type, getModel } from "@earendil-works/pi-ai";
|
|
14
14
|
import { MemoryProvider, RealFSProvider, ShadowProvider, VM, VmCheckpoint, createHttpHooks, createShadowPathPredicate, ensureImageSelector, loadGuestAssets } from "@earendil-works/gondolin";
|
|
15
|
+
import { DatabaseSync } from "node:sqlite";
|
|
15
16
|
import { once } from "node:events";
|
|
16
17
|
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
|
|
17
18
|
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
@@ -3044,10 +3045,10 @@ function validateRubricWeights(rubric) {
|
|
|
3044
3045
|
* complementary places.
|
|
3045
3046
|
*
|
|
3046
3047
|
* Before this envelope existed, criteria were scattered: a vestigial
|
|
3047
|
-
* `criteriaCid` column nobody resolved,
|
|
3048
|
-
*
|
|
3049
|
-
*
|
|
3050
|
-
*
|
|
3048
|
+
* `criteriaCid` column nobody resolved, free-form prose on
|
|
3049
|
+
* `fulfill_brief.input`, and inline `rubric` / `criteria[]` fields on
|
|
3050
|
+
* judgment-task inputs. None of those were machine-verifiable
|
|
3051
|
+
* end-to-end.
|
|
3051
3052
|
*
|
|
3052
3053
|
* This module defines a single, content-addressable envelope an imposer
|
|
3053
3054
|
* attaches to any task type. It has four orthogonal sections — pick
|
|
@@ -4184,7 +4185,6 @@ var FULFILL_BRIEF_TYPE = "fulfill_brief";
|
|
|
4184
4185
|
var FulfillBriefInput = Type$2.Object({
|
|
4185
4186
|
brief: Type$2.String({ minLength: 1 }),
|
|
4186
4187
|
title: Type$2.Optional(Type$2.String()),
|
|
4187
|
-
acceptanceCriteria: Type$2.Optional(Type$2.Array(Type$2.String())),
|
|
4188
4188
|
successCriteria: Type$2.Optional(SuccessCriteria),
|
|
4189
4189
|
seedFiles: Type$2.Optional(Type$2.Array(Type$2.String())),
|
|
4190
4190
|
scopeHint: Type$2.Optional(Type$2.String())
|
|
@@ -4756,7 +4756,10 @@ var BUILT_IN_TASK_TYPES = {
|
|
|
4756
4756
|
inputSchema: FulfillBriefInput,
|
|
4757
4757
|
outputSchema: FulfillBriefOutput,
|
|
4758
4758
|
outputKind: "artifact",
|
|
4759
|
+
resumable: true,
|
|
4759
4760
|
workspaceMode: "dedicated_worktree",
|
|
4761
|
+
workspaceScope: "session",
|
|
4762
|
+
sessionScope: "correlation",
|
|
4760
4763
|
requiresReferences: false,
|
|
4761
4764
|
validateOutput: requireVerificationWhenCriteriaPresent
|
|
4762
4765
|
},
|
|
@@ -4766,6 +4769,8 @@ var BUILT_IN_TASK_TYPES = {
|
|
|
4766
4769
|
outputSchema: AssessBriefOutput,
|
|
4767
4770
|
outputKind: "judgment",
|
|
4768
4771
|
workspaceMode: "dedicated_worktree",
|
|
4772
|
+
workspaceScope: "attempt",
|
|
4773
|
+
sessionScope: "none",
|
|
4769
4774
|
requiresReferences: true,
|
|
4770
4775
|
validateInput: validateJudgmentInput,
|
|
4771
4776
|
validateInputAsync: validateAssessBriefInputAsync
|
|
@@ -4775,6 +4780,8 @@ var BUILT_IN_TASK_TYPES = {
|
|
|
4775
4780
|
inputSchema: CuratePackInput,
|
|
4776
4781
|
outputSchema: CuratePackOutput,
|
|
4777
4782
|
outputKind: "artifact",
|
|
4783
|
+
workspaceScope: "attempt",
|
|
4784
|
+
sessionScope: "none",
|
|
4778
4785
|
requiresReferences: false,
|
|
4779
4786
|
validateOutput: requireVerificationWhenCriteriaPresent
|
|
4780
4787
|
},
|
|
@@ -4783,6 +4790,8 @@ var BUILT_IN_TASK_TYPES = {
|
|
|
4783
4790
|
inputSchema: RenderPackInput,
|
|
4784
4791
|
outputSchema: RenderPackOutput,
|
|
4785
4792
|
outputKind: "artifact",
|
|
4793
|
+
workspaceScope: "attempt",
|
|
4794
|
+
sessionScope: "none",
|
|
4786
4795
|
requiresReferences: false,
|
|
4787
4796
|
validateOutput: requireVerificationWhenCriteriaPresent,
|
|
4788
4797
|
validateInputAsync: validateRenderPackInputAsync
|
|
@@ -4792,6 +4801,8 @@ var BUILT_IN_TASK_TYPES = {
|
|
|
4792
4801
|
inputSchema: JudgePackInput,
|
|
4793
4802
|
outputSchema: JudgePackOutput,
|
|
4794
4803
|
outputKind: "judgment",
|
|
4804
|
+
workspaceScope: "attempt",
|
|
4805
|
+
sessionScope: "none",
|
|
4795
4806
|
requiresReferences: true,
|
|
4796
4807
|
validateInput: validateJudgmentInput,
|
|
4797
4808
|
validateOutput: validateJudgePackOutput,
|
|
@@ -4802,6 +4813,8 @@ var BUILT_IN_TASK_TYPES = {
|
|
|
4802
4813
|
inputSchema: RunEvalInput,
|
|
4803
4814
|
outputSchema: RunEvalOutput,
|
|
4804
4815
|
outputKind: "artifact",
|
|
4816
|
+
workspaceScope: "attempt",
|
|
4817
|
+
sessionScope: "custom",
|
|
4805
4818
|
requiresReferences: false,
|
|
4806
4819
|
validateOutput: validateRunEvalOutput
|
|
4807
4820
|
},
|
|
@@ -4810,6 +4823,8 @@ var BUILT_IN_TASK_TYPES = {
|
|
|
4810
4823
|
inputSchema: JudgeEvalVariantInput,
|
|
4811
4824
|
outputSchema: JudgeEvalVariantOutput,
|
|
4812
4825
|
outputKind: "judgment",
|
|
4826
|
+
workspaceScope: "attempt",
|
|
4827
|
+
sessionScope: "custom",
|
|
4813
4828
|
requiresReferences: false,
|
|
4814
4829
|
validateInput: validateJudgeEvalVariantInput,
|
|
4815
4830
|
validateOutput: validateJudgeEvalVariantOutput,
|
|
@@ -5732,6 +5747,49 @@ function taskTypeUsesSubagents(taskType) {
|
|
|
5732
5747
|
function taskTypeWorkspaceMode(taskType) {
|
|
5733
5748
|
return getTaskTypeEntry(taskType)?.workspaceMode ?? "shared_mount";
|
|
5734
5749
|
}
|
|
5750
|
+
/**
|
|
5751
|
+
* Whether the daemon may keep a warm executor session alive after one
|
|
5752
|
+
* attempt completes and reuse it for later related tasks.
|
|
5753
|
+
*
|
|
5754
|
+
* Unknown task types default to `false`.
|
|
5755
|
+
*/
|
|
5756
|
+
function taskTypeResumable(taskType) {
|
|
5757
|
+
return getTaskTypeEntry(taskType)?.resumable === true;
|
|
5758
|
+
}
|
|
5759
|
+
/**
|
|
5760
|
+
* Lifetime of the task type's workspace.
|
|
5761
|
+
*
|
|
5762
|
+
* Unknown task types and task types without an explicit policy default to the
|
|
5763
|
+
* disposable per-attempt behavior.
|
|
5764
|
+
*/
|
|
5765
|
+
function taskTypeWorkspaceScope(taskType) {
|
|
5766
|
+
return getTaskTypeEntry(taskType)?.workspaceScope ?? "attempt";
|
|
5767
|
+
}
|
|
5768
|
+
/**
|
|
5769
|
+
* Granularity of daemon-local warm-session reuse.
|
|
5770
|
+
*
|
|
5771
|
+
* Unknown task types and task types without an explicit policy default to no
|
|
5772
|
+
* reuse.
|
|
5773
|
+
*/
|
|
5774
|
+
function taskTypeSessionScope(taskType) {
|
|
5775
|
+
return getTaskTypeEntry(taskType)?.sessionScope ?? "none";
|
|
5776
|
+
}
|
|
5777
|
+
/**
|
|
5778
|
+
* Resolve the daemon-relevant execution policy declared by a task type.
|
|
5779
|
+
*
|
|
5780
|
+
* This is the single read path for "may this task reuse a warm session?" and
|
|
5781
|
+
* "does its workspace belong to the attempt or the session?" so daemon code
|
|
5782
|
+
* doesn't need to hand-roll defaults or peek at the raw registry shape.
|
|
5783
|
+
*/
|
|
5784
|
+
function getTaskExecutionPolicy(taskType) {
|
|
5785
|
+
return {
|
|
5786
|
+
resumable: taskTypeResumable(taskType),
|
|
5787
|
+
workspaceMode: taskTypeWorkspaceMode(taskType),
|
|
5788
|
+
workspaceScope: taskTypeWorkspaceScope(taskType),
|
|
5789
|
+
sessionScope: taskTypeSessionScope(taskType),
|
|
5790
|
+
usesSubagents: taskTypeUsesSubagents(taskType)
|
|
5791
|
+
};
|
|
5792
|
+
}
|
|
5735
5793
|
//#endregion
|
|
5736
5794
|
//#region ../../libs/tasks/src/wire.ts
|
|
5737
5795
|
/**
|
|
@@ -5994,6 +6052,10 @@ var COMMON_OPTIONAL_FLAGS = `\
|
|
|
5994
6052
|
this, the pi session aborts and the attempt
|
|
5995
6053
|
fails with code 'max_bash_timeouts_exceeded'.
|
|
5996
6054
|
0 = disabled. Default: 3.
|
|
6055
|
+
--warm-session-ttl-sec <n> Retain resumable daemon slots (Pi sessions +
|
|
6056
|
+
reusable worktrees) in local daemon state for
|
|
6057
|
+
this many seconds after use. 0 = disable reuse.
|
|
6058
|
+
Default: 1800.
|
|
5997
6059
|
--debug Verbose logging: also log successful list/claim
|
|
5998
6060
|
outcomes (candidate counts, claim attempts).`;
|
|
5999
6061
|
var REGISTERED_TASK_TYPES = Object.keys(BUILT_IN_TASK_TYPES).sort();
|
|
@@ -6106,78 +6168,6 @@ function isHelpFlag(args) {
|
|
|
6106
6168
|
return args.includes("--help") || args.includes("-h");
|
|
6107
6169
|
}
|
|
6108
6170
|
//#endregion
|
|
6109
|
-
//#region ../../libs/agent-runtime/src/subagent-output-contracts.ts
|
|
6110
|
-
var REGISTRY = /* @__PURE__ */ new Map();
|
|
6111
|
-
/**
|
|
6112
|
-
* Register a subagent output contract. Idempotent: re-registering the
|
|
6113
|
-
* same name with a different schema throws — contracts are meant to
|
|
6114
|
-
* be stable. Re-registering with the identical contract object (same
|
|
6115
|
-
* reference) is a no-op for HMR and test convenience.
|
|
6116
|
-
*
|
|
6117
|
-
* Typically called at module-init time alongside task-type
|
|
6118
|
-
* registration. See task-types/index.ts in @moltnet/tasks for the
|
|
6119
|
-
* conventional pattern.
|
|
6120
|
-
*/
|
|
6121
|
-
function registerSubagentOutputContract(contract) {
|
|
6122
|
-
if (!contract.name || contract.name.trim().length === 0) throw new Error("subagent output contract name is required");
|
|
6123
|
-
if (!/^[a-z][a-z0-9_]*$/.test(contract.name)) throw new Error(`subagent output contract name '${contract.name}' must be lower_snake_case (starts with a letter, then [a-z0-9_]+)`);
|
|
6124
|
-
const existing = REGISTRY.get(contract.name);
|
|
6125
|
-
if (existing && existing !== contract) {
|
|
6126
|
-
if (existing.parametersSchema !== contract.parametersSchema) throw new Error(`subagent output contract '${contract.name}' is already registered with a different schema; refusing to override`);
|
|
6127
|
-
}
|
|
6128
|
-
REGISTRY.set(contract.name, contract);
|
|
6129
|
-
}
|
|
6130
|
-
/**
|
|
6131
|
-
* Resolve a subagent output contract by name. Returns `null` for
|
|
6132
|
-
* unknown names — callers (the subagent custom tool) decide whether
|
|
6133
|
-
* that's a tool error the parent LLM can recover from or a hard fail.
|
|
6134
|
-
*/
|
|
6135
|
-
function getSubagentOutputContract(name) {
|
|
6136
|
-
return REGISTRY.get(name) ?? null;
|
|
6137
|
-
}
|
|
6138
|
-
/**
|
|
6139
|
-
* List all registered contracts. Useful for diagnostics and for the
|
|
6140
|
-
* subagent tool's parameter description so a parent LLM can see what
|
|
6141
|
-
* contracts are available without enumerating them in its prompt.
|
|
6142
|
-
*/
|
|
6143
|
-
function listSubagentOutputContracts() {
|
|
6144
|
-
return [...REGISTRY.values()];
|
|
6145
|
-
}
|
|
6146
|
-
//#endregion
|
|
6147
|
-
//#region ../../libs/agent-runtime/src/built-in-contract-registrations.ts
|
|
6148
|
-
/**
|
|
6149
|
-
* Built-in subagent output contracts (#1087, #943).
|
|
6150
|
-
*
|
|
6151
|
-
* Why this is an exported function and not a module-init side
|
|
6152
|
-
* effect:
|
|
6153
|
-
*
|
|
6154
|
-
* - The registry is process-global. Module-init registration
|
|
6155
|
-
* fires exactly once per Node process (ESM modules are cached
|
|
6156
|
-
* by URL). Tests that call `__resetSubagentOutputContractsForTests()`
|
|
6157
|
-
* to start from an empty registry have no way to repopulate
|
|
6158
|
-
* the built-ins without re-evaluating the module — which the
|
|
6159
|
-
* cache prevents. PR #1101 review M4.
|
|
6160
|
-
* - An explicit `registerBuiltInSubagentContracts()` lets the
|
|
6161
|
-
* package index call it once at module load AND lets test
|
|
6162
|
-
* setup hooks call it again after `__reset...`.
|
|
6163
|
-
* - `registerSubagentOutputContract` is itself idempotent for
|
|
6164
|
-
* identical re-registrations, so calling this function twice
|
|
6165
|
-
* in the same process is safe.
|
|
6166
|
-
*
|
|
6167
|
-
* Adding a new built-in: extend the body of this function. Do not
|
|
6168
|
-
* call `registerSubagentOutputContract` from anywhere else in the
|
|
6169
|
-
* package — keeping all built-ins in one function makes the set
|
|
6170
|
-
* auditable.
|
|
6171
|
-
*/
|
|
6172
|
-
function registerBuiltInSubagentContracts() {
|
|
6173
|
-
registerSubagentOutputContract({
|
|
6174
|
-
name: "judge_eval_variant_result",
|
|
6175
|
-
description: "Per-variant grading result produced by a subagent of judge_eval_variant: scores against the shared rubric, composite, and a 1-3 sentence verdict for a single variant.",
|
|
6176
|
-
parametersSchema: JudgeEvalVariantResult
|
|
6177
|
-
});
|
|
6178
|
-
}
|
|
6179
|
-
registerBuiltInSubagentContracts();
|
|
6180
|
-
//#endregion
|
|
6181
6171
|
//#region ../../libs/agent-runtime/src/context-bindings.ts
|
|
6182
6172
|
var PROMPT_SEPARATOR = "\n\n---\n\n";
|
|
6183
6173
|
/**
|
|
@@ -6637,13 +6627,7 @@ function buildCuratePackUserPrompt(input, ctx) {
|
|
|
6637
6627
|
* is told to inspect them itself.
|
|
6638
6628
|
*/
|
|
6639
6629
|
function buildFulfillBriefUserPrompt(input, ctx) {
|
|
6640
|
-
const { brief, title,
|
|
6641
|
-
const criteriaSection = acceptanceCriteria?.length ? [
|
|
6642
|
-
"### Acceptance criteria",
|
|
6643
|
-
"",
|
|
6644
|
-
...acceptanceCriteria.map((c) => `- ${c}`),
|
|
6645
|
-
""
|
|
6646
|
-
].join("\n") : "";
|
|
6630
|
+
const { brief, title, seedFiles, scopeHint } = input;
|
|
6647
6631
|
const seedSection = seedFiles?.length ? [
|
|
6648
6632
|
"### Seed files",
|
|
6649
6633
|
"",
|
|
@@ -6691,7 +6675,6 @@ function buildFulfillBriefUserPrompt(input, ctx) {
|
|
|
6691
6675
|
"",
|
|
6692
6676
|
brief,
|
|
6693
6677
|
"",
|
|
6694
|
-
criteriaSection,
|
|
6695
6678
|
seedSection,
|
|
6696
6679
|
correlationSection,
|
|
6697
6680
|
workspaceSection,
|
|
@@ -13853,6 +13836,31 @@ function abortableSleep(ms, signal) {
|
|
|
13853
13836
|
});
|
|
13854
13837
|
}
|
|
13855
13838
|
//#endregion
|
|
13839
|
+
//#region ../../libs/agent-runtime/src/subagent-output-contracts.ts
|
|
13840
|
+
/**
|
|
13841
|
+
* Construct an immutable contract registry from a static list.
|
|
13842
|
+
*
|
|
13843
|
+
* The resulting registry is safe to share across sessions and
|
|
13844
|
+
* invocations — no mutation is possible after construction.
|
|
13845
|
+
*/
|
|
13846
|
+
function createSubagentContractRegistry(contracts) {
|
|
13847
|
+
const lookup = /* @__PURE__ */ new Map();
|
|
13848
|
+
for (const c of contracts) {
|
|
13849
|
+
if (!c.name || c.name.trim().length === 0) throw new Error("subagent output contract name is required");
|
|
13850
|
+
if (!/^[a-z][a-z0-9_]*$/.test(c.name)) throw new Error(`subagent output contract name '${c.name}' must be lower_snake_case (starts with a letter, then [a-z0-9_]+)`);
|
|
13851
|
+
if (lookup.has(c.name)) throw new Error(`duplicate subagent output contract name '${c.name}' in constructor args`);
|
|
13852
|
+
lookup.set(c.name, c);
|
|
13853
|
+
}
|
|
13854
|
+
return {
|
|
13855
|
+
get(name) {
|
|
13856
|
+
return lookup.get(name) ?? null;
|
|
13857
|
+
},
|
|
13858
|
+
list() {
|
|
13859
|
+
return [...lookup.values()];
|
|
13860
|
+
}
|
|
13861
|
+
};
|
|
13862
|
+
}
|
|
13863
|
+
//#endregion
|
|
13856
13864
|
//#region ../../libs/pi-extension/src/moltnet/render-phase6.ts
|
|
13857
13865
|
function slugToTitle(value) {
|
|
13858
13866
|
return value.split(/[:/_-]+/).filter(Boolean).map((part) => part[0]?.toUpperCase() + part.slice(1)).join(" ");
|
|
@@ -14829,7 +14837,8 @@ function findMainWorktree() {
|
|
|
14829
14837
|
function loadCredentials(agentDir) {
|
|
14830
14838
|
const moltnetJson = readFileSync(path.join(agentDir, "moltnet.json"), "utf8");
|
|
14831
14839
|
const agentEnvRaw = readFileSync(path.join(agentDir, "env"), "utf8");
|
|
14832
|
-
const
|
|
14840
|
+
const piAgentDir = process.env.PI_CODING_AGENT_DIR ?? path.join(process.env.HOME ?? "", ".pi", "agent");
|
|
14841
|
+
const piAuthPath = path.join(piAgentDir, "auth.json");
|
|
14833
14842
|
const piAuthJson = existsSync(piAuthPath) ? readFileSync(piAuthPath, "utf8") : null;
|
|
14834
14843
|
const gitconfigPath = path.join(agentDir, "gitconfig");
|
|
14835
14844
|
const gitconfig = existsSync(gitconfigPath) ? readFileSync(gitconfigPath, "utf8") : null;
|
|
@@ -15329,9 +15338,11 @@ var NO_SKILLS = () => ({
|
|
|
15329
15338
|
diagnostics: []
|
|
15330
15339
|
});
|
|
15331
15340
|
/**
|
|
15332
|
-
* Construct an
|
|
15333
|
-
*
|
|
15334
|
-
*
|
|
15341
|
+
* Construct an `AgentSession`. By default it is in-memory; callers may opt
|
|
15342
|
+
* parent sessions into daemon-owned file persistence via `sessionPersistence`.
|
|
15343
|
+
* The caller is responsible for eventually invoking `session.prompt(...)` and
|
|
15344
|
+
* for tearing down — the helper does no lifecycle management beyond
|
|
15345
|
+
* construction.
|
|
15335
15346
|
*/
|
|
15336
15347
|
async function buildAgentSession(args) {
|
|
15337
15348
|
const piOtelExtension = createPiOtelExtension({
|
|
@@ -15339,22 +15350,30 @@ async function buildAgentSession(args) {
|
|
|
15339
15350
|
spanAttributes: args.otelSpanAttrs
|
|
15340
15351
|
});
|
|
15341
15352
|
const resourceLoader = new DefaultResourceLoader({
|
|
15342
|
-
cwd: args.
|
|
15353
|
+
cwd: args.cwdPath,
|
|
15343
15354
|
agentDir: args.piAuthDir,
|
|
15344
15355
|
extensionFactories: [piOtelExtension],
|
|
15345
15356
|
appendSystemPrompt: args.appendSystemPrompt,
|
|
15346
15357
|
skillsOverride: args.skillsOverride ?? NO_SKILLS
|
|
15347
15358
|
});
|
|
15348
15359
|
await resourceLoader.reload();
|
|
15360
|
+
const sessionManager = args.sessionPersistence ? await resolvePersistentSessionManager({
|
|
15361
|
+
cwd: args.cwdPath,
|
|
15362
|
+
sessionDir: args.sessionPersistence.sessionDir
|
|
15363
|
+
}) : SessionManager.inMemory(args.cwdPath);
|
|
15349
15364
|
return (await createAgentSession({
|
|
15350
15365
|
agentDir: args.piAuthDir,
|
|
15351
|
-
cwd: args.
|
|
15366
|
+
cwd: args.cwdPath,
|
|
15352
15367
|
model: args.modelHandle,
|
|
15353
15368
|
customTools: args.customTools,
|
|
15354
|
-
sessionManager
|
|
15369
|
+
sessionManager,
|
|
15355
15370
|
resourceLoader
|
|
15356
15371
|
})).session;
|
|
15357
15372
|
}
|
|
15373
|
+
async function resolvePersistentSessionManager(args) {
|
|
15374
|
+
await SessionManager.list(args.cwd, args.sessionDir);
|
|
15375
|
+
return SessionManager.continueRecent(args.cwd, args.sessionDir);
|
|
15376
|
+
}
|
|
15358
15377
|
//#endregion
|
|
15359
15378
|
//#region ../../libs/pi-extension/src/runtime/inject-task-context.ts
|
|
15360
15379
|
/**
|
|
@@ -15589,6 +15608,7 @@ var DEFAULT_SUBAGENT_TIMEOUT_MS = 300 * 1e3;
|
|
|
15589
15608
|
*/
|
|
15590
15609
|
function createSubagentTool(args) {
|
|
15591
15610
|
const buildSession = args.buildAgentSession ?? buildAgentSession;
|
|
15611
|
+
const { contractRegistry } = args;
|
|
15592
15612
|
let callCount = 0;
|
|
15593
15613
|
return {
|
|
15594
15614
|
tool: defineTool({
|
|
@@ -15599,8 +15619,8 @@ function createSubagentTool(args) {
|
|
|
15599
15619
|
async execute(_id, params) {
|
|
15600
15620
|
if (!Check(SubagentToolParameters, params)) return toolError(`subagent: invalid parameters: ${JSON.stringify([...Errors(SubagentToolParameters, params)].slice(0, 3))}`);
|
|
15601
15621
|
const { task, output_schema } = params;
|
|
15602
|
-
const contract =
|
|
15603
|
-
if (!contract) return toolError(`subagent: unknown output_schema "${output_schema}". Registered contracts: [${
|
|
15622
|
+
const contract = contractRegistry.get(output_schema);
|
|
15623
|
+
if (!contract) return toolError(`subagent: unknown output_schema "${output_schema}". Registered contracts: [${contractRegistry.list().map((c) => c.name).join(", ")}]`);
|
|
15604
15624
|
callCount += 1;
|
|
15605
15625
|
const callIndex = callCount;
|
|
15606
15626
|
let captured = null;
|
|
@@ -15630,6 +15650,7 @@ function createSubagentTool(args) {
|
|
|
15630
15650
|
});
|
|
15631
15651
|
const session = await buildSession({
|
|
15632
15652
|
mountPath: args.mountPath,
|
|
15653
|
+
cwdPath: args.cwdPath ?? args.mountPath,
|
|
15633
15654
|
piAuthDir: args.piAuthDir,
|
|
15634
15655
|
modelHandle: args.modelHandle,
|
|
15635
15656
|
agentName: args.agentName,
|
|
@@ -15965,6 +15986,115 @@ function resolveSubmitTools(taskType, opts = {}) {
|
|
|
15965
15986
|
};
|
|
15966
15987
|
}
|
|
15967
15988
|
//#endregion
|
|
15989
|
+
//#region ../../libs/pi-extension/src/runtime/task-workspace.ts
|
|
15990
|
+
function prepareTaskWorkspace(task, requestedMountPath, executionPlan) {
|
|
15991
|
+
const branch = executionPlan?.worktreeBranch ?? null;
|
|
15992
|
+
if (!branch) return {
|
|
15993
|
+
mountPath: requestedMountPath,
|
|
15994
|
+
cwdPath: requestedMountPath,
|
|
15995
|
+
mode: "shared_mount",
|
|
15996
|
+
branch: null,
|
|
15997
|
+
cleanup: () => {}
|
|
15998
|
+
};
|
|
15999
|
+
const mainRepo = findMainWorktree();
|
|
16000
|
+
const worktreeDir = resolveTaskWorktreePath(mainRepo, executionPlan?.workspaceId ?? `task-${task.id}`);
|
|
16001
|
+
const relMount = relative(mainRepo, requestedMountPath);
|
|
16002
|
+
const cwdPath = relMount === "" || relMount.startsWith("..") ? worktreeDir : join(worktreeDir, relMount);
|
|
16003
|
+
const keepWorkspace = executionPlan?.workspaceScope === "session" && executionPlan.sessionKey !== null;
|
|
16004
|
+
if (keepWorkspace) ensureReusableTaskWorktree(mainRepo, worktreeDir, branch);
|
|
16005
|
+
else {
|
|
16006
|
+
removeExistingTaskWorktree(mainRepo, worktreeDir);
|
|
16007
|
+
addTaskWorktree(mainRepo, worktreeDir, branch);
|
|
16008
|
+
}
|
|
16009
|
+
return {
|
|
16010
|
+
mountPath: mainRepo,
|
|
16011
|
+
cwdPath,
|
|
16012
|
+
mode: "dedicated_worktree",
|
|
16013
|
+
branch,
|
|
16014
|
+
cleanup: keepWorkspace ? () => {} : () => {
|
|
16015
|
+
execFileSync("git", [
|
|
16016
|
+
"-C",
|
|
16017
|
+
mainRepo,
|
|
16018
|
+
"worktree",
|
|
16019
|
+
"remove",
|
|
16020
|
+
"--force",
|
|
16021
|
+
worktreeDir
|
|
16022
|
+
], { stdio: "pipe" });
|
|
16023
|
+
}
|
|
16024
|
+
};
|
|
16025
|
+
}
|
|
16026
|
+
function resolveTaskWorktreePath(mainRepo, workspaceId) {
|
|
16027
|
+
return join(mainRepo, ".worktrees", workspaceId);
|
|
16028
|
+
}
|
|
16029
|
+
function ensureReusableTaskWorktree(mainRepo, worktreeDir, branch) {
|
|
16030
|
+
if (isRegisteredWorktree$1(mainRepo, worktreeDir)) return;
|
|
16031
|
+
if (existsSync(worktreeDir)) throw new Error(`Expected reusable worktree ${worktreeDir} to be git-managed, but it exists outside git worktree metadata.`);
|
|
16032
|
+
addTaskWorktree(mainRepo, worktreeDir, branch);
|
|
16033
|
+
}
|
|
16034
|
+
function addTaskWorktree(mainRepo, worktreeDir, branch) {
|
|
16035
|
+
const baseRef = resolveWorktreeBaseRef(mainRepo);
|
|
16036
|
+
execFileSync("git", gitRefExists(mainRepo, `refs/heads/${branch}`) ? [
|
|
16037
|
+
"-C",
|
|
16038
|
+
mainRepo,
|
|
16039
|
+
"worktree",
|
|
16040
|
+
"add",
|
|
16041
|
+
worktreeDir,
|
|
16042
|
+
branch
|
|
16043
|
+
] : [
|
|
16044
|
+
"-C",
|
|
16045
|
+
mainRepo,
|
|
16046
|
+
"worktree",
|
|
16047
|
+
"add",
|
|
16048
|
+
"-b",
|
|
16049
|
+
branch,
|
|
16050
|
+
worktreeDir,
|
|
16051
|
+
baseRef
|
|
16052
|
+
], { stdio: "pipe" });
|
|
16053
|
+
}
|
|
16054
|
+
function removeExistingTaskWorktree(mainRepo, worktreeDir) {
|
|
16055
|
+
if (!existsSync(worktreeDir) || !isRegisteredWorktree$1(mainRepo, worktreeDir)) return;
|
|
16056
|
+
execFileSync("git", [
|
|
16057
|
+
"-C",
|
|
16058
|
+
mainRepo,
|
|
16059
|
+
"worktree",
|
|
16060
|
+
"remove",
|
|
16061
|
+
"--force",
|
|
16062
|
+
worktreeDir
|
|
16063
|
+
], { stdio: "pipe" });
|
|
16064
|
+
}
|
|
16065
|
+
function isRegisteredWorktree$1(mainRepo, worktreeDir) {
|
|
16066
|
+
const list = execFileSync("git", [
|
|
16067
|
+
"-C",
|
|
16068
|
+
mainRepo,
|
|
16069
|
+
"worktree",
|
|
16070
|
+
"list",
|
|
16071
|
+
"--porcelain"
|
|
16072
|
+
], {
|
|
16073
|
+
encoding: "utf8",
|
|
16074
|
+
stdio: "pipe"
|
|
16075
|
+
});
|
|
16076
|
+
const marker = `worktree ${worktreeDir}\n`;
|
|
16077
|
+
return list.includes(marker) || list.endsWith(`worktree ${worktreeDir}`);
|
|
16078
|
+
}
|
|
16079
|
+
function resolveWorktreeBaseRef(mainRepo) {
|
|
16080
|
+
return gitRefExists(mainRepo, "refs/heads/main") ? "main" : "HEAD";
|
|
16081
|
+
}
|
|
16082
|
+
function gitRefExists(mainRepo, ref) {
|
|
16083
|
+
try {
|
|
16084
|
+
execFileSync("git", [
|
|
16085
|
+
"-C",
|
|
16086
|
+
mainRepo,
|
|
16087
|
+
"show-ref",
|
|
16088
|
+
"--verify",
|
|
16089
|
+
"--quiet",
|
|
16090
|
+
ref
|
|
16091
|
+
], { stdio: "pipe" });
|
|
16092
|
+
return true;
|
|
16093
|
+
} catch {
|
|
16094
|
+
return false;
|
|
16095
|
+
}
|
|
16096
|
+
}
|
|
16097
|
+
//#endregion
|
|
15968
16098
|
//#region ../../libs/pi-extension/src/runtime/execute-pi-task.ts
|
|
15969
16099
|
/**
|
|
15970
16100
|
* executePiTask — run a single Task attempt using pi-coding-agent inside a
|
|
@@ -15991,15 +16121,24 @@ var noopTurnEventHandler = () => {};
|
|
|
15991
16121
|
function createPiTaskExecutor(opts) {
|
|
15992
16122
|
let cachedCheckpoint = opts.checkpointPath ?? null;
|
|
15993
16123
|
return async (claimedTask, reporter) => {
|
|
15994
|
-
|
|
15995
|
-
|
|
15996
|
-
|
|
15997
|
-
|
|
15998
|
-
})
|
|
16124
|
+
const reporterWasOpened = !reporter.cancelSignal.aborted;
|
|
16125
|
+
if (reporterWasOpened) await reporter.open({
|
|
16126
|
+
taskId: claimedTask.task.id,
|
|
16127
|
+
attemptN: claimedTask.attemptN
|
|
15999
16128
|
});
|
|
16000
16129
|
return executePiTask(claimedTask, reporter, {
|
|
16001
16130
|
...opts,
|
|
16002
|
-
checkpointPath: cachedCheckpoint
|
|
16131
|
+
checkpointPath: cachedCheckpoint ?? void 0,
|
|
16132
|
+
resolveCheckpointPath: async () => {
|
|
16133
|
+
if (!cachedCheckpoint) cachedCheckpoint = await ensureSnapshot({
|
|
16134
|
+
config: opts.sandboxConfig?.snapshot,
|
|
16135
|
+
onProgress: opts.onSnapshotProgress ?? ((m) => {
|
|
16136
|
+
process.stderr.write(`[snapshot] ${m}\n`);
|
|
16137
|
+
})
|
|
16138
|
+
});
|
|
16139
|
+
return cachedCheckpoint;
|
|
16140
|
+
},
|
|
16141
|
+
reporterAlreadyOpened: reporterWasOpened
|
|
16003
16142
|
});
|
|
16004
16143
|
};
|
|
16005
16144
|
}
|
|
@@ -16013,8 +16152,11 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
16013
16152
|
const task = claimedTask.task;
|
|
16014
16153
|
const attemptN = claimedTask.attemptN;
|
|
16015
16154
|
const startTime = Date.now();
|
|
16016
|
-
const
|
|
16017
|
-
const
|
|
16155
|
+
const requestedMountPath = opts.mountPath ?? process.cwd();
|
|
16156
|
+
const executionPlan = opts.makeExecutionPlan?.(claimedTask) ?? null;
|
|
16157
|
+
let workspace = null;
|
|
16158
|
+
let mountPath = requestedMountPath;
|
|
16159
|
+
let cwdPath = requestedMountPath;
|
|
16018
16160
|
if (reporter.cancelSignal.aborted) return {
|
|
16019
16161
|
taskId: task.id,
|
|
16020
16162
|
attemptN,
|
|
@@ -16029,33 +16171,8 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
16029
16171
|
retryable: false
|
|
16030
16172
|
}
|
|
16031
16173
|
};
|
|
16032
|
-
|
|
16033
|
-
config: opts.sandboxConfig?.snapshot,
|
|
16034
|
-
onProgress: opts.onSnapshotProgress ?? ((m) => {
|
|
16035
|
-
process.stderr.write(`[snapshot] ${m}\n`);
|
|
16036
|
-
})
|
|
16037
|
-
});
|
|
16038
|
-
const mainRepoForRepair = findMainWorktree();
|
|
16039
|
-
try {
|
|
16040
|
-
execFileSync("git", [
|
|
16041
|
-
"-C",
|
|
16042
|
-
mainRepoForRepair,
|
|
16043
|
-
"worktree",
|
|
16044
|
-
"repair",
|
|
16045
|
-
"--relative-paths"
|
|
16046
|
-
], { stdio: "pipe" });
|
|
16047
|
-
} catch {}
|
|
16174
|
+
let reporterOpen = opts.reporterAlreadyOpened ?? false;
|
|
16048
16175
|
let managed = null;
|
|
16049
|
-
managed = await resumeVm({
|
|
16050
|
-
checkpointPath,
|
|
16051
|
-
agentName: opts.agentName,
|
|
16052
|
-
mountPath,
|
|
16053
|
-
extraAllowedHosts: opts.extraAllowedHosts,
|
|
16054
|
-
sandboxConfig: opts.sandboxConfig
|
|
16055
|
-
});
|
|
16056
|
-
const diaryId = task.diaryId ?? "";
|
|
16057
|
-
const taskTeamId = task.teamId ?? "";
|
|
16058
|
-
let reporterOpen = false;
|
|
16059
16176
|
let session = null;
|
|
16060
16177
|
let subagentHandle = null;
|
|
16061
16178
|
const finalUsage = emptyUsage(opts.provider, opts.model);
|
|
@@ -16074,41 +16191,103 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
16074
16191
|
retryable: false
|
|
16075
16192
|
}
|
|
16076
16193
|
});
|
|
16194
|
+
let onTurnEvent;
|
|
16195
|
+
if (opts.makeOnTurnEvent) try {
|
|
16196
|
+
onTurnEvent = opts.makeOnTurnEvent(claimedTask);
|
|
16197
|
+
} catch (err) {
|
|
16198
|
+
process.stderr.write(`[emit] makeOnTurnEvent threw: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
16199
|
+
onTurnEvent = noopTurnEventHandler;
|
|
16200
|
+
}
|
|
16201
|
+
else onTurnEvent = opts.onTurnEvent ?? noopTurnEventHandler;
|
|
16202
|
+
const emit = (kind, payload) => {
|
|
16203
|
+
try {
|
|
16204
|
+
onTurnEvent(kind, summarizePayloadForLog(kind, payload));
|
|
16205
|
+
} catch (err) {
|
|
16206
|
+
process.stderr.write(`[emit] onTurnEvent threw for kind="${kind}": ${err instanceof Error ? err.message : String(err)}\n`);
|
|
16207
|
+
}
|
|
16208
|
+
return reporter.record({
|
|
16209
|
+
kind,
|
|
16210
|
+
payload
|
|
16211
|
+
});
|
|
16212
|
+
};
|
|
16213
|
+
const emitError = async (phase, message, extra = {}) => {
|
|
16214
|
+
await emit("error", {
|
|
16215
|
+
phase,
|
|
16216
|
+
message,
|
|
16217
|
+
...extra
|
|
16218
|
+
});
|
|
16219
|
+
};
|
|
16077
16220
|
try {
|
|
16078
|
-
|
|
16079
|
-
activateAgentEnv(managed.credentials.agentEnv, mainRepo);
|
|
16080
|
-
await reporter.open({
|
|
16221
|
+
if (!opts.reporterAlreadyOpened) await reporter.open({
|
|
16081
16222
|
taskId: task.id,
|
|
16082
16223
|
attemptN
|
|
16083
16224
|
});
|
|
16084
16225
|
reporterOpen = true;
|
|
16085
|
-
let
|
|
16086
|
-
|
|
16087
|
-
|
|
16226
|
+
let checkpointPath;
|
|
16227
|
+
try {
|
|
16228
|
+
checkpointPath = opts.checkpointPath ?? (opts.resolveCheckpointPath ? await opts.resolveCheckpointPath() : await ensureSnapshot({
|
|
16229
|
+
config: opts.sandboxConfig?.snapshot,
|
|
16230
|
+
onProgress: opts.onSnapshotProgress ?? ((m) => {
|
|
16231
|
+
process.stderr.write(`[snapshot] ${m}\n`);
|
|
16232
|
+
})
|
|
16233
|
+
}));
|
|
16088
16234
|
} catch (err) {
|
|
16089
|
-
|
|
16090
|
-
|
|
16235
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
16236
|
+
await emitError("snapshot", message);
|
|
16237
|
+
return makeFailedOutput("snapshot_failed", message);
|
|
16091
16238
|
}
|
|
16092
|
-
|
|
16093
|
-
|
|
16239
|
+
try {
|
|
16240
|
+
workspace = prepareTaskWorkspace(task, requestedMountPath, executionPlan);
|
|
16241
|
+
mountPath = workspace.mountPath;
|
|
16242
|
+
cwdPath = workspace.cwdPath;
|
|
16243
|
+
} catch (err) {
|
|
16244
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
16245
|
+
await emitError("worktree_setup", message);
|
|
16246
|
+
return makeFailedOutput("worktree_setup_failed", message);
|
|
16247
|
+
}
|
|
16248
|
+
try {
|
|
16249
|
+
const mainRepoForRepair = findMainWorktree();
|
|
16094
16250
|
try {
|
|
16095
|
-
|
|
16096
|
-
|
|
16097
|
-
|
|
16098
|
-
|
|
16099
|
-
|
|
16100
|
-
|
|
16101
|
-
|
|
16251
|
+
execFileSync("git", [
|
|
16252
|
+
"-C",
|
|
16253
|
+
mainRepoForRepair,
|
|
16254
|
+
"worktree",
|
|
16255
|
+
"repair",
|
|
16256
|
+
"--relative-paths"
|
|
16257
|
+
], { stdio: "pipe" });
|
|
16258
|
+
} catch {}
|
|
16259
|
+
} catch (err) {
|
|
16260
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
16261
|
+
await emitError("worktree_setup", message);
|
|
16262
|
+
return makeFailedOutput("worktree_setup_failed", message);
|
|
16263
|
+
}
|
|
16264
|
+
try {
|
|
16265
|
+
managed = await resumeVm({
|
|
16266
|
+
checkpointPath,
|
|
16267
|
+
agentName: opts.agentName,
|
|
16268
|
+
mountPath,
|
|
16269
|
+
extraAllowedHosts: opts.extraAllowedHosts,
|
|
16270
|
+
sandboxConfig: opts.sandboxConfig
|
|
16102
16271
|
});
|
|
16103
|
-
}
|
|
16272
|
+
} catch (err) {
|
|
16273
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
16274
|
+
await emitError("vm_resume", message);
|
|
16275
|
+
return makeFailedOutput("vm_resume_failed", message);
|
|
16276
|
+
}
|
|
16277
|
+
const diaryId = task.diaryId ?? "";
|
|
16278
|
+
const taskTeamId = task.teamId ?? "";
|
|
16279
|
+
const mainRepo = findMainWorktree();
|
|
16280
|
+
activateAgentEnv(managed.credentials.agentEnv, mainRepo);
|
|
16281
|
+
const activeWorkspace = workspace;
|
|
16282
|
+
if (!activeWorkspace) throw new Error("task workspace not prepared");
|
|
16104
16283
|
await emit("info", {
|
|
16105
16284
|
event: "execute_start",
|
|
16106
16285
|
taskType: task.taskType,
|
|
16107
16286
|
teamId: task.teamId,
|
|
16108
16287
|
provider: opts.provider,
|
|
16109
16288
|
model: opts.model,
|
|
16110
|
-
workspaceMode:
|
|
16111
|
-
workspaceBranch:
|
|
16289
|
+
workspaceMode: activeWorkspace.mode,
|
|
16290
|
+
workspaceBranch: activeWorkspace.branch
|
|
16112
16291
|
});
|
|
16113
16292
|
let taskPrompt;
|
|
16114
16293
|
try {
|
|
@@ -16116,8 +16295,8 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
16116
16295
|
diaryId,
|
|
16117
16296
|
taskId: task.id,
|
|
16118
16297
|
workspace: {
|
|
16119
|
-
mode:
|
|
16120
|
-
branch:
|
|
16298
|
+
mode: activeWorkspace.mode,
|
|
16299
|
+
branch: activeWorkspace.branch
|
|
16121
16300
|
},
|
|
16122
16301
|
extras: opts.promptExtras
|
|
16123
16302
|
});
|
|
@@ -16169,7 +16348,7 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
16169
16348
|
getTeamId: () => taskTeamId,
|
|
16170
16349
|
getSessionErrors: () => [],
|
|
16171
16350
|
clearSessionErrors: () => {},
|
|
16172
|
-
getHostCwd: () =>
|
|
16351
|
+
getHostCwd: () => cwdPath,
|
|
16173
16352
|
hostExecBaseEnv: new Set([...HOST_EXEC_DEFAULT_BASE_ENV, ...Object.keys(managed.credentials.agentEnv)]),
|
|
16174
16353
|
hostExecAutoApprove: opts.hostExecAutoApprove ?? opts.sandboxConfig?.hostExec?.autoApprove ?? false,
|
|
16175
16354
|
getTaskContext: () => ({
|
|
@@ -16197,6 +16376,7 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
16197
16376
|
if (taskTypeUsesSubagents(task.taskType)) {
|
|
16198
16377
|
subagentHandle = createSubagentTool({
|
|
16199
16378
|
mountPath,
|
|
16379
|
+
cwdPath,
|
|
16200
16380
|
piAuthDir,
|
|
16201
16381
|
modelHandle,
|
|
16202
16382
|
agentName: opts.agentName,
|
|
@@ -16205,12 +16385,14 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
16205
16385
|
parentTaskId: task.id,
|
|
16206
16386
|
parentTaskType: task.taskType,
|
|
16207
16387
|
parentAttemptN: attemptN,
|
|
16388
|
+
contractRegistry: opts.subagentContractRegistry,
|
|
16208
16389
|
parentCancelSignal: reporter.cancelSignal
|
|
16209
16390
|
});
|
|
16210
16391
|
parentSubagentTools.push(subagentHandle.tool);
|
|
16211
16392
|
}
|
|
16212
16393
|
session = await buildAgentSession({
|
|
16213
16394
|
mountPath,
|
|
16395
|
+
cwdPath,
|
|
16214
16396
|
piAuthDir,
|
|
16215
16397
|
modelHandle,
|
|
16216
16398
|
agentName: opts.agentName,
|
|
@@ -16229,7 +16411,8 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
16229
16411
|
"moltnet.task.id": task.id,
|
|
16230
16412
|
"moltnet.task.attempt": attemptN,
|
|
16231
16413
|
"moltnet.task.type": task.taskType
|
|
16232
|
-
}
|
|
16414
|
+
},
|
|
16415
|
+
sessionPersistence: executionPlan?.sessionPersistence ?? void 0
|
|
16233
16416
|
});
|
|
16234
16417
|
} catch (err) {
|
|
16235
16418
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -16294,6 +16477,10 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
16294
16477
|
is_error: event.isError,
|
|
16295
16478
|
result: event.isError ? truncateForWire(event.result) : void 0
|
|
16296
16479
|
}));
|
|
16480
|
+
if (event.isError) track(emitError("tool_call_error", describeToolErrorMessage(event.result), {
|
|
16481
|
+
tool: event.toolName,
|
|
16482
|
+
result: truncateForWire(event.result)
|
|
16483
|
+
}));
|
|
16297
16484
|
if (maxBashTimeouts > 0 && event.toolName === "bash" && event.isError && isBashTimeoutResult(event.result)) {
|
|
16298
16485
|
bashTimeoutCount += 1;
|
|
16299
16486
|
if (bashTimeoutCount >= maxBashTimeouts) triggerCapAbort("max_bash_timeouts_exceeded", `Aborted after ${bashTimeoutCount} bash timeouts in this attempt (cap ${maxBashTimeouts}).`);
|
|
@@ -16449,7 +16636,7 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
16449
16636
|
}
|
|
16450
16637
|
}
|
|
16451
16638
|
if (managed) await managed.vm.close();
|
|
16452
|
-
try {
|
|
16639
|
+
if (workspace) try {
|
|
16453
16640
|
workspace.cleanup();
|
|
16454
16641
|
} catch (err) {
|
|
16455
16642
|
const detail = err instanceof Error ? err.message : String(err);
|
|
@@ -16457,107 +16644,6 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
16457
16644
|
}
|
|
16458
16645
|
}
|
|
16459
16646
|
}
|
|
16460
|
-
function resolveTaskWorktreeBranch(task) {
|
|
16461
|
-
if (taskTypeWorkspaceMode(task.taskType) !== "dedicated_worktree") return null;
|
|
16462
|
-
if (task.taskType === "fulfill_brief") {
|
|
16463
|
-
const input = task.input;
|
|
16464
|
-
const slug = slugifyBranchComponent(typeof input.title === "string" && input.title.trim().length > 0 ? input.title : typeof input.brief === "string" && input.brief.trim().length > 0 ? input.brief : task.taskType) || "task";
|
|
16465
|
-
if (task.correlationId) return `moltnet/${task.correlationId}/${slug}`;
|
|
16466
|
-
return `feat/${(typeof input.scopeHint === "string" && input.scopeHint.trim().length > 0 ? slugifyBranchComponent(input.scopeHint) : "task") || "task"}-${slug}`;
|
|
16467
|
-
}
|
|
16468
|
-
return `task/${slugifyBranchComponent(task.taskType) || "task"}-${task.id.slice(0, 8)}`;
|
|
16469
|
-
}
|
|
16470
|
-
function slugifyBranchComponent(input) {
|
|
16471
|
-
return input.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60).replace(/-+$/g, "");
|
|
16472
|
-
}
|
|
16473
|
-
function prepareTaskWorkspace(task, requestedMountPath) {
|
|
16474
|
-
const branch = resolveTaskWorktreeBranch(task);
|
|
16475
|
-
if (!branch) return {
|
|
16476
|
-
mountPath: requestedMountPath,
|
|
16477
|
-
mode: "shared_mount",
|
|
16478
|
-
branch: null,
|
|
16479
|
-
cleanup: () => {}
|
|
16480
|
-
};
|
|
16481
|
-
const mainRepo = findMainWorktree();
|
|
16482
|
-
const worktreeDir = join(mainRepo, ".worktrees", `task-${task.id}`);
|
|
16483
|
-
removeExistingTaskWorktree(mainRepo, worktreeDir);
|
|
16484
|
-
const relMount = relative(mainRepo, requestedMountPath);
|
|
16485
|
-
const mountPath = relMount === "" || relMount.startsWith("..") ? worktreeDir : join(worktreeDir, relMount);
|
|
16486
|
-
const baseRef = resolveWorktreeBaseRef(mainRepo);
|
|
16487
|
-
execFileSync("git", gitRefExists(mainRepo, `refs/heads/${branch}`) ? [
|
|
16488
|
-
"-C",
|
|
16489
|
-
mainRepo,
|
|
16490
|
-
"worktree",
|
|
16491
|
-
"add",
|
|
16492
|
-
worktreeDir,
|
|
16493
|
-
branch
|
|
16494
|
-
] : [
|
|
16495
|
-
"-C",
|
|
16496
|
-
mainRepo,
|
|
16497
|
-
"worktree",
|
|
16498
|
-
"add",
|
|
16499
|
-
"-b",
|
|
16500
|
-
branch,
|
|
16501
|
-
worktreeDir,
|
|
16502
|
-
baseRef
|
|
16503
|
-
], { stdio: "pipe" });
|
|
16504
|
-
return {
|
|
16505
|
-
mountPath,
|
|
16506
|
-
mode: "dedicated_worktree",
|
|
16507
|
-
branch,
|
|
16508
|
-
cleanup: () => {
|
|
16509
|
-
execFileSync("git", [
|
|
16510
|
-
"-C",
|
|
16511
|
-
mainRepo,
|
|
16512
|
-
"worktree",
|
|
16513
|
-
"remove",
|
|
16514
|
-
"--force",
|
|
16515
|
-
worktreeDir
|
|
16516
|
-
], { stdio: "pipe" });
|
|
16517
|
-
}
|
|
16518
|
-
};
|
|
16519
|
-
}
|
|
16520
|
-
function removeExistingTaskWorktree(mainRepo, worktreeDir) {
|
|
16521
|
-
if (!existsSync(worktreeDir)) return;
|
|
16522
|
-
const list = execFileSync("git", [
|
|
16523
|
-
"-C",
|
|
16524
|
-
mainRepo,
|
|
16525
|
-
"worktree",
|
|
16526
|
-
"list",
|
|
16527
|
-
"--porcelain"
|
|
16528
|
-
], {
|
|
16529
|
-
encoding: "utf8",
|
|
16530
|
-
stdio: "pipe"
|
|
16531
|
-
});
|
|
16532
|
-
const marker = `worktree ${worktreeDir}\n`;
|
|
16533
|
-
if (!list.includes(marker) && !list.endsWith(`worktree ${worktreeDir}`)) return;
|
|
16534
|
-
execFileSync("git", [
|
|
16535
|
-
"-C",
|
|
16536
|
-
mainRepo,
|
|
16537
|
-
"worktree",
|
|
16538
|
-
"remove",
|
|
16539
|
-
"--force",
|
|
16540
|
-
worktreeDir
|
|
16541
|
-
], { stdio: "pipe" });
|
|
16542
|
-
}
|
|
16543
|
-
function resolveWorktreeBaseRef(mainRepo) {
|
|
16544
|
-
return gitRefExists(mainRepo, "refs/heads/main") ? "main" : "HEAD";
|
|
16545
|
-
}
|
|
16546
|
-
function gitRefExists(mainRepo, ref) {
|
|
16547
|
-
try {
|
|
16548
|
-
execFileSync("git", [
|
|
16549
|
-
"-C",
|
|
16550
|
-
mainRepo,
|
|
16551
|
-
"show-ref",
|
|
16552
|
-
"--verify",
|
|
16553
|
-
"--quiet",
|
|
16554
|
-
ref
|
|
16555
|
-
], { stdio: "pipe" });
|
|
16556
|
-
return true;
|
|
16557
|
-
} catch {
|
|
16558
|
-
return false;
|
|
16559
|
-
}
|
|
16560
|
-
}
|
|
16561
16647
|
function emptyUsage(provider, model) {
|
|
16562
16648
|
return {
|
|
16563
16649
|
inputTokens: 0,
|
|
@@ -16661,6 +16747,23 @@ function truncateForWire(value) {
|
|
|
16661
16747
|
};
|
|
16662
16748
|
}
|
|
16663
16749
|
}
|
|
16750
|
+
function describeToolErrorMessage(result) {
|
|
16751
|
+
if (typeof result === "string" && result.trim().length > 0) return result.trim();
|
|
16752
|
+
if (result && typeof result === "object") {
|
|
16753
|
+
const content = result.content;
|
|
16754
|
+
if (Array.isArray(content)) {
|
|
16755
|
+
for (const item of content) if (item && typeof item === "object" && typeof item.text === "string") {
|
|
16756
|
+
const text = item.text.trim();
|
|
16757
|
+
if (text.length > 0) return text;
|
|
16758
|
+
}
|
|
16759
|
+
}
|
|
16760
|
+
}
|
|
16761
|
+
try {
|
|
16762
|
+
return JSON.stringify(truncateForWire(result));
|
|
16763
|
+
} catch {
|
|
16764
|
+
return "Tool call failed";
|
|
16765
|
+
}
|
|
16766
|
+
}
|
|
16664
16767
|
//#endregion
|
|
16665
16768
|
//#region src/config.ts
|
|
16666
16769
|
function loadConfig() {
|
|
@@ -16757,6 +16860,419 @@ function createGhCliClient() {
|
|
|
16757
16860
|
};
|
|
16758
16861
|
}
|
|
16759
16862
|
//#endregion
|
|
16863
|
+
//#region src/lib/daemon-slot-registry.ts
|
|
16864
|
+
var DaemonSlotRegistryError = class extends Error {
|
|
16865
|
+
constructor(operation, cause) {
|
|
16866
|
+
super(`Daemon slot registry failed during ${operation}`, { cause });
|
|
16867
|
+
this.operation = operation;
|
|
16868
|
+
this.name = "DaemonSlotRegistryError";
|
|
16869
|
+
}
|
|
16870
|
+
};
|
|
16871
|
+
var DaemonSlotRegistry = class {
|
|
16872
|
+
db;
|
|
16873
|
+
constructor(dbPath) {
|
|
16874
|
+
try {
|
|
16875
|
+
this.db = new DatabaseSync(dbPath);
|
|
16876
|
+
this.withDb("initialize schema", () => {
|
|
16877
|
+
this.db.exec(`
|
|
16878
|
+
PRAGMA journal_mode = WAL;
|
|
16879
|
+
|
|
16880
|
+
CREATE TABLE IF NOT EXISTS daemon_slots (
|
|
16881
|
+
agent_name TEXT NOT NULL,
|
|
16882
|
+
provider TEXT NOT NULL,
|
|
16883
|
+
model TEXT NOT NULL,
|
|
16884
|
+
slot_key TEXT NOT NULL,
|
|
16885
|
+
task_type TEXT NOT NULL,
|
|
16886
|
+
state TEXT NOT NULL CHECK (state IN ('active', 'idle')),
|
|
16887
|
+
last_task_id TEXT NOT NULL,
|
|
16888
|
+
last_attempt_n INTEGER NOT NULL,
|
|
16889
|
+
created_at_ms INTEGER NOT NULL,
|
|
16890
|
+
last_used_at_ms INTEGER NOT NULL,
|
|
16891
|
+
expires_at_ms INTEGER NOT NULL,
|
|
16892
|
+
PRIMARY KEY (agent_name, provider, model, slot_key)
|
|
16893
|
+
);
|
|
16894
|
+
|
|
16895
|
+
CREATE TABLE IF NOT EXISTS daemon_slot_sessions (
|
|
16896
|
+
agent_name TEXT NOT NULL,
|
|
16897
|
+
provider TEXT NOT NULL,
|
|
16898
|
+
model TEXT NOT NULL,
|
|
16899
|
+
slot_key TEXT NOT NULL,
|
|
16900
|
+
session_dir TEXT NOT NULL UNIQUE,
|
|
16901
|
+
session_path TEXT,
|
|
16902
|
+
PRIMARY KEY (agent_name, provider, model, slot_key),
|
|
16903
|
+
FOREIGN KEY (agent_name, provider, model, slot_key)
|
|
16904
|
+
REFERENCES daemon_slots(agent_name, provider, model, slot_key)
|
|
16905
|
+
ON DELETE CASCADE
|
|
16906
|
+
);
|
|
16907
|
+
|
|
16908
|
+
CREATE TABLE IF NOT EXISTS daemon_slot_workspaces (
|
|
16909
|
+
agent_name TEXT NOT NULL,
|
|
16910
|
+
provider TEXT NOT NULL,
|
|
16911
|
+
model TEXT NOT NULL,
|
|
16912
|
+
slot_key TEXT NOT NULL,
|
|
16913
|
+
workspace_id TEXT NOT NULL UNIQUE,
|
|
16914
|
+
worktree_path TEXT NOT NULL,
|
|
16915
|
+
worktree_branch TEXT,
|
|
16916
|
+
PRIMARY KEY (agent_name, provider, model, slot_key),
|
|
16917
|
+
FOREIGN KEY (agent_name, provider, model, slot_key)
|
|
16918
|
+
REFERENCES daemon_slots(agent_name, provider, model, slot_key)
|
|
16919
|
+
ON DELETE CASCADE
|
|
16920
|
+
);
|
|
16921
|
+
|
|
16922
|
+
CREATE INDEX IF NOT EXISTS daemon_slots_expires_idx
|
|
16923
|
+
ON daemon_slots (expires_at_ms);
|
|
16924
|
+
`);
|
|
16925
|
+
});
|
|
16926
|
+
} catch (error) {
|
|
16927
|
+
throw new DaemonSlotRegistryError("open database", error);
|
|
16928
|
+
}
|
|
16929
|
+
}
|
|
16930
|
+
close() {
|
|
16931
|
+
try {
|
|
16932
|
+
this.db.close();
|
|
16933
|
+
} catch (error) {
|
|
16934
|
+
throw new DaemonSlotRegistryError("close database", error);
|
|
16935
|
+
}
|
|
16936
|
+
}
|
|
16937
|
+
beginSlot(input) {
|
|
16938
|
+
const now = Date.now();
|
|
16939
|
+
const expiresAtMs = now + input.ttlSec * 1e3;
|
|
16940
|
+
this.withDb("upsert slot", () => this.db.prepare(`INSERT INTO daemon_slots (
|
|
16941
|
+
agent_name, provider, model, slot_key, task_type, state,
|
|
16942
|
+
last_task_id, last_attempt_n, created_at_ms, last_used_at_ms,
|
|
16943
|
+
expires_at_ms
|
|
16944
|
+
) VALUES (
|
|
16945
|
+
:agentName, :provider, :model, :slotKey, :taskType, 'active',
|
|
16946
|
+
:lastTaskId, :lastAttemptN, :createdAtMs, :lastUsedAtMs,
|
|
16947
|
+
:expiresAtMs
|
|
16948
|
+
)
|
|
16949
|
+
ON CONFLICT(agent_name, provider, model, slot_key) DO UPDATE SET
|
|
16950
|
+
task_type = excluded.task_type,
|
|
16951
|
+
state = 'active',
|
|
16952
|
+
last_task_id = excluded.last_task_id,
|
|
16953
|
+
last_attempt_n = excluded.last_attempt_n,
|
|
16954
|
+
last_used_at_ms = excluded.last_used_at_ms,
|
|
16955
|
+
expires_at_ms = excluded.expires_at_ms`).run({
|
|
16956
|
+
agentName: input.agentName,
|
|
16957
|
+
provider: input.provider,
|
|
16958
|
+
model: input.model,
|
|
16959
|
+
slotKey: input.slotKey,
|
|
16960
|
+
taskType: input.taskType,
|
|
16961
|
+
lastTaskId: input.lastTaskId,
|
|
16962
|
+
lastAttemptN: input.lastAttemptN,
|
|
16963
|
+
createdAtMs: now,
|
|
16964
|
+
lastUsedAtMs: now,
|
|
16965
|
+
expiresAtMs
|
|
16966
|
+
}));
|
|
16967
|
+
if (input.sessionDir) this.withDb("upsert slot session", () => this.db.prepare(`INSERT INTO daemon_slot_sessions (
|
|
16968
|
+
agent_name, provider, model, slot_key, session_dir, session_path
|
|
16969
|
+
) VALUES (
|
|
16970
|
+
:agentName, :provider, :model, :slotKey, :sessionDir, :sessionPath
|
|
16971
|
+
)
|
|
16972
|
+
ON CONFLICT(agent_name, provider, model, slot_key) DO UPDATE SET
|
|
16973
|
+
session_dir = excluded.session_dir,
|
|
16974
|
+
session_path = excluded.session_path`).run({
|
|
16975
|
+
agentName: input.agentName,
|
|
16976
|
+
provider: input.provider,
|
|
16977
|
+
model: input.model,
|
|
16978
|
+
slotKey: input.slotKey,
|
|
16979
|
+
sessionDir: input.sessionDir,
|
|
16980
|
+
sessionPath: input.sessionPath
|
|
16981
|
+
}));
|
|
16982
|
+
if (input.workspaceId && input.worktreePath) this.withDb("upsert slot workspace", () => this.db.prepare(`INSERT INTO daemon_slot_workspaces (
|
|
16983
|
+
agent_name, provider, model, slot_key, workspace_id, worktree_path,
|
|
16984
|
+
worktree_branch
|
|
16985
|
+
) VALUES (
|
|
16986
|
+
:agentName, :provider, :model, :slotKey, :workspaceId, :worktreePath,
|
|
16987
|
+
:worktreeBranch
|
|
16988
|
+
)
|
|
16989
|
+
ON CONFLICT(agent_name, provider, model, slot_key) DO UPDATE SET
|
|
16990
|
+
workspace_id = excluded.workspace_id,
|
|
16991
|
+
worktree_path = excluded.worktree_path,
|
|
16992
|
+
worktree_branch = excluded.worktree_branch`).run({
|
|
16993
|
+
agentName: input.agentName,
|
|
16994
|
+
provider: input.provider,
|
|
16995
|
+
model: input.model,
|
|
16996
|
+
slotKey: input.slotKey,
|
|
16997
|
+
workspaceId: input.workspaceId,
|
|
16998
|
+
worktreePath: input.worktreePath,
|
|
16999
|
+
worktreeBranch: input.worktreeBranch
|
|
17000
|
+
}));
|
|
17001
|
+
}
|
|
17002
|
+
finishSlot(identity, slotKey, ttlSec, sessionPath) {
|
|
17003
|
+
const now = Date.now();
|
|
17004
|
+
this.withDb("finish slot", () => this.db.prepare(`UPDATE daemon_slots
|
|
17005
|
+
SET state = 'idle',
|
|
17006
|
+
last_used_at_ms = ?,
|
|
17007
|
+
expires_at_ms = ?
|
|
17008
|
+
WHERE agent_name = ? AND provider = ? AND model = ? AND slot_key = ?`).run(now, now + ttlSec * 1e3, identity.agentName, identity.provider, identity.model, slotKey));
|
|
17009
|
+
if (sessionPath !== null) this.withDb("update slot session path", () => this.db.prepare(`UPDATE daemon_slot_sessions
|
|
17010
|
+
SET session_path = ?
|
|
17011
|
+
WHERE agent_name = ? AND provider = ? AND model = ? AND slot_key = ?`).run(sessionPath, identity.agentName, identity.provider, identity.model, slotKey));
|
|
17012
|
+
}
|
|
17013
|
+
reapExpiredSlots(now = Date.now()) {
|
|
17014
|
+
this.withDb("begin reap transaction", () => this.db.exec("BEGIN IMMEDIATE"));
|
|
17015
|
+
try {
|
|
17016
|
+
const slots = this.withDb("select expired slots", () => this.db.prepare(`SELECT
|
|
17017
|
+
agent_name as agentName,
|
|
17018
|
+
provider,
|
|
17019
|
+
model,
|
|
17020
|
+
slot_key as slotKey,
|
|
17021
|
+
task_type as taskType,
|
|
17022
|
+
state,
|
|
17023
|
+
last_task_id as lastTaskId,
|
|
17024
|
+
last_attempt_n as lastAttemptN,
|
|
17025
|
+
created_at_ms as createdAtMs,
|
|
17026
|
+
last_used_at_ms as lastUsedAtMs,
|
|
17027
|
+
expires_at_ms as expiresAtMs
|
|
17028
|
+
FROM daemon_slots
|
|
17029
|
+
WHERE expires_at_ms <= ?`).all(now));
|
|
17030
|
+
if (slots.length < 1) {
|
|
17031
|
+
this.withDb("commit empty reap transaction", () => this.db.exec("COMMIT"));
|
|
17032
|
+
return [];
|
|
17033
|
+
}
|
|
17034
|
+
const selectSession = this.withDb("prepare slot session lookup", () => this.db.prepare(`SELECT
|
|
17035
|
+
agent_name as agentName,
|
|
17036
|
+
provider,
|
|
17037
|
+
model,
|
|
17038
|
+
slot_key as slotKey,
|
|
17039
|
+
session_dir as sessionDir,
|
|
17040
|
+
session_path as sessionPath
|
|
17041
|
+
FROM daemon_slot_sessions
|
|
17042
|
+
WHERE agent_name = ? AND provider = ? AND model = ? AND slot_key = ?`));
|
|
17043
|
+
const selectWorkspace = this.withDb("prepare slot workspace lookup", () => this.db.prepare(`SELECT
|
|
17044
|
+
agent_name as agentName,
|
|
17045
|
+
provider,
|
|
17046
|
+
model,
|
|
17047
|
+
slot_key as slotKey,
|
|
17048
|
+
workspace_id as workspaceId,
|
|
17049
|
+
worktree_path as worktreePath,
|
|
17050
|
+
worktree_branch as worktreeBranch
|
|
17051
|
+
FROM daemon_slot_workspaces
|
|
17052
|
+
WHERE agent_name = ? AND provider = ? AND model = ? AND slot_key = ?`));
|
|
17053
|
+
const deleteStmt = this.withDb("prepare expired slot delete", () => this.db.prepare("DELETE FROM daemon_slots WHERE agent_name = ? AND provider = ? AND model = ? AND slot_key = ?"));
|
|
17054
|
+
const out = [];
|
|
17055
|
+
for (const slot of slots) {
|
|
17056
|
+
const session = this.withDb("select slot session", () => selectSession.get(slot.agentName, slot.provider, slot.model, slot.slotKey) ?? null);
|
|
17057
|
+
const workspace = this.withDb("select slot workspace", () => selectWorkspace.get(slot.agentName, slot.provider, slot.model, slot.slotKey) ?? null);
|
|
17058
|
+
out.push({
|
|
17059
|
+
slot,
|
|
17060
|
+
session,
|
|
17061
|
+
workspace
|
|
17062
|
+
});
|
|
17063
|
+
this.withDb("delete expired slot", () => deleteStmt.run(slot.agentName, slot.provider, slot.model, slot.slotKey));
|
|
17064
|
+
}
|
|
17065
|
+
this.withDb("commit reap transaction", () => this.db.exec("COMMIT"));
|
|
17066
|
+
for (const item of out) {
|
|
17067
|
+
if (item.session) cleanupPiSessionDir(item.session.sessionDir);
|
|
17068
|
+
if (item.workspace) cleanupReusableWorktree(item.workspace.worktreePath);
|
|
17069
|
+
}
|
|
17070
|
+
return out;
|
|
17071
|
+
} catch (error) {
|
|
17072
|
+
try {
|
|
17073
|
+
this.db.exec("ROLLBACK");
|
|
17074
|
+
} catch {}
|
|
17075
|
+
throw error instanceof DaemonSlotRegistryError ? error : new DaemonSlotRegistryError("reap expired slots", error);
|
|
17076
|
+
}
|
|
17077
|
+
}
|
|
17078
|
+
withDb(operation, fn) {
|
|
17079
|
+
try {
|
|
17080
|
+
return fn();
|
|
17081
|
+
} catch (error) {
|
|
17082
|
+
throw new DaemonSlotRegistryError(operation, error);
|
|
17083
|
+
}
|
|
17084
|
+
}
|
|
17085
|
+
};
|
|
17086
|
+
function resolveLatestPiSessionPath(sessionDir) {
|
|
17087
|
+
try {
|
|
17088
|
+
const latestEntry = readdirSync(sessionDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) => entry.name).sort().at(-1);
|
|
17089
|
+
return latestEntry ? join(sessionDir, latestEntry) : null;
|
|
17090
|
+
} catch {
|
|
17091
|
+
return null;
|
|
17092
|
+
}
|
|
17093
|
+
}
|
|
17094
|
+
function cleanupPiSessionDir(sessionDir) {
|
|
17095
|
+
rmSync(sessionDir, {
|
|
17096
|
+
recursive: true,
|
|
17097
|
+
force: true
|
|
17098
|
+
});
|
|
17099
|
+
}
|
|
17100
|
+
function cleanupReusableWorktree(worktreePath) {
|
|
17101
|
+
const mainRepo = findMainWorktree();
|
|
17102
|
+
if (isRegisteredWorktree(mainRepo, worktreePath)) {
|
|
17103
|
+
execFileSync("git", [
|
|
17104
|
+
"-C",
|
|
17105
|
+
mainRepo,
|
|
17106
|
+
"worktree",
|
|
17107
|
+
"remove",
|
|
17108
|
+
"--force",
|
|
17109
|
+
worktreePath
|
|
17110
|
+
], { stdio: "pipe" });
|
|
17111
|
+
return;
|
|
17112
|
+
}
|
|
17113
|
+
rmSync(worktreePath, {
|
|
17114
|
+
recursive: true,
|
|
17115
|
+
force: true
|
|
17116
|
+
});
|
|
17117
|
+
}
|
|
17118
|
+
function isRegisteredWorktree(mainRepo, worktreeDir) {
|
|
17119
|
+
const list = execFileSync("git", [
|
|
17120
|
+
"-C",
|
|
17121
|
+
mainRepo,
|
|
17122
|
+
"worktree",
|
|
17123
|
+
"list",
|
|
17124
|
+
"--porcelain"
|
|
17125
|
+
], {
|
|
17126
|
+
encoding: "utf8",
|
|
17127
|
+
stdio: "pipe"
|
|
17128
|
+
});
|
|
17129
|
+
const marker = `worktree ${worktreeDir}\n`;
|
|
17130
|
+
return list.includes(marker) || list.endsWith(`worktree ${worktreeDir}`);
|
|
17131
|
+
}
|
|
17132
|
+
//#endregion
|
|
17133
|
+
//#region src/lib/slugify.ts
|
|
17134
|
+
function slugifyAsciiLower(input, maxLen, preserveChars = []) {
|
|
17135
|
+
const preserved = new Set(preserveChars.map((char) => char.toLowerCase()));
|
|
17136
|
+
let out = "";
|
|
17137
|
+
let pendingDash = false;
|
|
17138
|
+
for (const rawChar of input) {
|
|
17139
|
+
const char = rawChar.toLowerCase();
|
|
17140
|
+
const isAlphaNum = char >= "a" && char <= "z" || char >= "0" && char <= "9";
|
|
17141
|
+
const isPreserved = preserved.has(char);
|
|
17142
|
+
if (isAlphaNum || isPreserved) {
|
|
17143
|
+
if (pendingDash && out.length > 0 && out.length < maxLen) out += "-";
|
|
17144
|
+
pendingDash = false;
|
|
17145
|
+
if (out.length < maxLen) out += char;
|
|
17146
|
+
else break;
|
|
17147
|
+
continue;
|
|
17148
|
+
}
|
|
17149
|
+
pendingDash = out.length > 0;
|
|
17150
|
+
}
|
|
17151
|
+
return out;
|
|
17152
|
+
}
|
|
17153
|
+
//#endregion
|
|
17154
|
+
//#region src/lib/session-policy.ts
|
|
17155
|
+
/**
|
|
17156
|
+
* Resolve the daemon-facing execution policy for a task and, when the task
|
|
17157
|
+
* type permits warm reuse, compute the daemon-local `sessionKey`.
|
|
17158
|
+
*
|
|
17159
|
+
* `correlationId` remains the audit/query key; `sessionKey` is narrower and
|
|
17160
|
+
* may fold in task-type-specific discriminators so eval/review flows stay
|
|
17161
|
+
* isolated even inside one correlation group.
|
|
17162
|
+
*/
|
|
17163
|
+
function deriveTaskSessionDescriptor(task) {
|
|
17164
|
+
const policy = getTaskExecutionPolicy(task.taskType);
|
|
17165
|
+
return {
|
|
17166
|
+
policy,
|
|
17167
|
+
sessionKey: buildTaskSessionKey(task, policy)
|
|
17168
|
+
};
|
|
17169
|
+
}
|
|
17170
|
+
function buildTaskSessionKey(task, policy) {
|
|
17171
|
+
if (!policy.resumable) return null;
|
|
17172
|
+
switch (policy.sessionScope) {
|
|
17173
|
+
case "none": return null;
|
|
17174
|
+
case "correlation": return task.correlationId ? `${task.taskType}:correlation:${task.correlationId}` : null;
|
|
17175
|
+
case "custom": return buildCustomSessionKey(task);
|
|
17176
|
+
}
|
|
17177
|
+
}
|
|
17178
|
+
function buildCustomSessionKey(task) {
|
|
17179
|
+
switch (task.taskType) {
|
|
17180
|
+
case "run_eval": {
|
|
17181
|
+
const variantLabel = typeof task.input.variantLabel === "string" ? task.input.variantLabel : null;
|
|
17182
|
+
if (!task.correlationId || !variantLabel) return null;
|
|
17183
|
+
return `run_eval:correlation:${task.correlationId}:variant:${slugifySessionComponent(variantLabel)}`;
|
|
17184
|
+
}
|
|
17185
|
+
case "judge_eval_variant": {
|
|
17186
|
+
const runTaskIds = Array.isArray(task.input.runTaskIds) ? task.input.runTaskIds.filter((value) => typeof value === "string") : [];
|
|
17187
|
+
if (runTaskIds.length < 1) return null;
|
|
17188
|
+
return `judge_eval_variant:run_tasks:${[...runTaskIds].sort().join(",")}`;
|
|
17189
|
+
}
|
|
17190
|
+
default: return null;
|
|
17191
|
+
}
|
|
17192
|
+
}
|
|
17193
|
+
function slugifySessionComponent(input) {
|
|
17194
|
+
return slugifyAsciiLower(input.trim(), 64);
|
|
17195
|
+
}
|
|
17196
|
+
//#endregion
|
|
17197
|
+
//#region src/lib/task-execution-plan.ts
|
|
17198
|
+
function buildDaemonTaskExecutionPlan(task, stateDirs, identity, warmSessionTtlSec) {
|
|
17199
|
+
const descriptor = deriveTaskSessionDescriptor(task);
|
|
17200
|
+
const slotKey = warmSessionTtlSec > 0 ? descriptor.sessionKey : null;
|
|
17201
|
+
const workspaceScope = slotKey !== null ? descriptor.policy.workspaceScope : "attempt";
|
|
17202
|
+
const slotId = slotKey ? buildDaemonSlotId(identity, slotKey) : null;
|
|
17203
|
+
const sessionDir = slotId ? `${stateDirs.piSessionsDir}/${encodeURIComponent(slotId)}` : null;
|
|
17204
|
+
const worktreeBranch = resolveTaskWorktreeBranch(task, descriptor.policy);
|
|
17205
|
+
const workspaceId = worktreeBranch !== null ? resolveTaskWorkspaceId(task, {
|
|
17206
|
+
sessionKey: slotId,
|
|
17207
|
+
workspaceScope,
|
|
17208
|
+
sessionPersistence: sessionDir ? { sessionDir } : null
|
|
17209
|
+
}) : null;
|
|
17210
|
+
return {
|
|
17211
|
+
descriptor,
|
|
17212
|
+
sessionKey: slotId,
|
|
17213
|
+
slotKey,
|
|
17214
|
+
slotId,
|
|
17215
|
+
workspaceScope,
|
|
17216
|
+
sessionPersistence: sessionDir ? { sessionDir } : null,
|
|
17217
|
+
workspaceId,
|
|
17218
|
+
worktreeBranch
|
|
17219
|
+
};
|
|
17220
|
+
}
|
|
17221
|
+
function buildDaemonSlotId(identity, slotKey) {
|
|
17222
|
+
return [
|
|
17223
|
+
"agent",
|
|
17224
|
+
slugSlotIdentityComponent(identity.agentName),
|
|
17225
|
+
"provider",
|
|
17226
|
+
slugSlotIdentityComponent(identity.provider),
|
|
17227
|
+
"model",
|
|
17228
|
+
slugSlotIdentityComponent(identity.model),
|
|
17229
|
+
"key",
|
|
17230
|
+
slotKey
|
|
17231
|
+
].join(":");
|
|
17232
|
+
}
|
|
17233
|
+
function slugSlotIdentityComponent(input) {
|
|
17234
|
+
return slugifyAsciiLower(input.trim(), 64, [
|
|
17235
|
+
".",
|
|
17236
|
+
"_",
|
|
17237
|
+
"-"
|
|
17238
|
+
]);
|
|
17239
|
+
}
|
|
17240
|
+
function resolveTaskWorktreeBranch(task, policy) {
|
|
17241
|
+
if (policy.workspaceMode !== "dedicated_worktree") return null;
|
|
17242
|
+
if (task.taskType === "fulfill_brief") {
|
|
17243
|
+
const input = task.input;
|
|
17244
|
+
const slug = slugifyAsciiLower(typeof input.title === "string" && input.title.trim().length > 0 ? input.title : typeof input.brief === "string" && input.brief.trim().length > 0 ? input.brief : task.taskType, 60) || "task";
|
|
17245
|
+
if (task.correlationId) return `moltnet/${task.correlationId}/${slug}`;
|
|
17246
|
+
return `feat/${(typeof input.scopeHint === "string" && input.scopeHint.trim().length > 0 ? slugifyAsciiLower(input.scopeHint, 60) : "task") || "task"}-${slug}`;
|
|
17247
|
+
}
|
|
17248
|
+
return `task/${slugifyAsciiLower(task.taskType, 60) || "task"}-${task.id.slice(0, 8)}`;
|
|
17249
|
+
}
|
|
17250
|
+
function resolveTaskWorkspaceId(task, executionPlan) {
|
|
17251
|
+
if (executionPlan.workspaceScope === "session" && executionPlan.sessionKey !== null) return `session-${encodeURIComponent(executionPlan.sessionKey)}`;
|
|
17252
|
+
return `task-${task.id}`;
|
|
17253
|
+
}
|
|
17254
|
+
//#endregion
|
|
17255
|
+
//#region src/lib/execution-plan-cache.ts
|
|
17256
|
+
function createExecutionPlanCache(args) {
|
|
17257
|
+
const cache = /* @__PURE__ */ new Map();
|
|
17258
|
+
return {
|
|
17259
|
+
getOrCreate(claimedTask) {
|
|
17260
|
+
const key = buildClaimedTaskKey(claimedTask);
|
|
17261
|
+
const existing = cache.get(key);
|
|
17262
|
+
if (existing) return existing;
|
|
17263
|
+
const plan = buildDaemonTaskExecutionPlan(claimedTask.task, args.stateDirs, args.slotIdentity, args.warmSessionTtlSec);
|
|
17264
|
+
cache.set(key, plan);
|
|
17265
|
+
return plan;
|
|
17266
|
+
},
|
|
17267
|
+
delete(claimedTask) {
|
|
17268
|
+
cache.delete(buildClaimedTaskKey(claimedTask));
|
|
17269
|
+
}
|
|
17270
|
+
};
|
|
17271
|
+
}
|
|
17272
|
+
function buildClaimedTaskKey(task) {
|
|
17273
|
+
return `${task.task.id}:${task.attemptN}`;
|
|
17274
|
+
}
|
|
17275
|
+
//#endregion
|
|
16760
17276
|
//#region src/lib/finalize.ts
|
|
16761
17277
|
async function finalizeTask(agent, output, ctx = {}) {
|
|
16762
17278
|
if (output.status === "cancelled") return;
|
|
@@ -16841,7 +17357,8 @@ var DEFAULTS = {
|
|
|
16841
17357
|
maxBatchSize: 50,
|
|
16842
17358
|
flushIntervalMs: 200,
|
|
16843
17359
|
maxTurns: 0,
|
|
16844
|
-
maxBashTimeouts: 3
|
|
17360
|
+
maxBashTimeouts: 3,
|
|
17361
|
+
warmSessionTtlSec: 1800
|
|
16845
17362
|
};
|
|
16846
17363
|
var MissingRequiredOptionError = class extends Error {
|
|
16847
17364
|
constructor(flag) {
|
|
@@ -16865,6 +17382,7 @@ function parseCommonOptions(args) {
|
|
|
16865
17382
|
flushIntervalMs: parseNonNegativeInt(args["flush-interval-ms"], "flush-interval-ms", DEFAULTS.flushIntervalMs),
|
|
16866
17383
|
maxTurns: parseNonNegativeInt(args["max-turns"], "max-turns", DEFAULTS.maxTurns),
|
|
16867
17384
|
maxBashTimeouts: parseNonNegativeInt(args["max-bash-timeouts"], "max-bash-timeouts", DEFAULTS.maxBashTimeouts),
|
|
17385
|
+
warmSessionTtlSec: parseNonNegativeInt(args["warm-session-ttl-sec"], "warm-session-ttl-sec", DEFAULTS.warmSessionTtlSec),
|
|
16868
17386
|
debug: args.debug === true
|
|
16869
17387
|
};
|
|
16870
17388
|
}
|
|
@@ -16900,6 +17418,7 @@ function commonOptionDefs() {
|
|
|
16900
17418
|
"flush-interval-ms": { type: "string" },
|
|
16901
17419
|
"max-turns": { type: "string" },
|
|
16902
17420
|
"max-bash-timeouts": { type: "string" },
|
|
17421
|
+
"warm-session-ttl-sec": { type: "string" },
|
|
16903
17422
|
debug: { type: "boolean" }
|
|
16904
17423
|
};
|
|
16905
17424
|
}
|
|
@@ -16982,6 +17501,18 @@ function findUp(startDir, filename) {
|
|
|
16982
17501
|
}
|
|
16983
17502
|
}
|
|
16984
17503
|
//#endregion
|
|
17504
|
+
//#region src/lib/state-dir.ts
|
|
17505
|
+
function ensureDaemonStateDirs(mountPath) {
|
|
17506
|
+
const rootDir = join(mountPath, ".moltnet", "d");
|
|
17507
|
+
const piSessionsDir = join(rootDir, "pi-sessions");
|
|
17508
|
+
mkdirSync(piSessionsDir, { recursive: true });
|
|
17509
|
+
return {
|
|
17510
|
+
rootDir,
|
|
17511
|
+
piSessionsDir,
|
|
17512
|
+
registryDbPath: join(rootDir, "daemon-state.sqlite")
|
|
17513
|
+
};
|
|
17514
|
+
}
|
|
17515
|
+
//#endregion
|
|
16985
17516
|
//#region src/lib/turn-event-logger.ts
|
|
16986
17517
|
function makeTurnEventHandler(base, context = {}) {
|
|
16987
17518
|
const log = base.child({
|
|
@@ -17053,6 +17584,19 @@ async function runPolling(opts) {
|
|
|
17053
17584
|
const listLimit = optionalPositiveInt(values["list-limit"], "list-limit", 10);
|
|
17054
17585
|
if (taskTypes.length === 0) console.error(`[${opts.modeLabel}] --task-types is empty — daemon will accept any registered type. Pass an explicit list to limit scope (e.g. --task-types fulfill_brief).`);
|
|
17055
17586
|
const sandbox = resolveSandbox(process.cwd(), values.sandbox);
|
|
17587
|
+
const stateDirs = ensureDaemonStateDirs(sandbox.rootDir);
|
|
17588
|
+
const slotRegistry = new DaemonSlotRegistry(stateDirs.registryDbPath);
|
|
17589
|
+
const slotIdentity = {
|
|
17590
|
+
agentName: common.agent,
|
|
17591
|
+
provider: common.provider,
|
|
17592
|
+
model: common.model
|
|
17593
|
+
};
|
|
17594
|
+
const mainRepo = findMainWorktree();
|
|
17595
|
+
const executionPlans = createExecutionPlanCache({
|
|
17596
|
+
stateDirs,
|
|
17597
|
+
slotIdentity,
|
|
17598
|
+
warmSessionTtlSec: common.warmSessionTtlSec
|
|
17599
|
+
});
|
|
17056
17600
|
const ctx = await resolveAgentContext(common.agent);
|
|
17057
17601
|
const cfg = loadConfig();
|
|
17058
17602
|
const otelShutdown = await initWorkerOtel({
|
|
@@ -17096,13 +17640,20 @@ async function runPolling(opts) {
|
|
|
17096
17640
|
maxPollIntervalMs
|
|
17097
17641
|
}, "agent-daemon.starting");
|
|
17098
17642
|
const outputs = [];
|
|
17643
|
+
const subagentContractRegistry = createSubagentContractRegistry([{
|
|
17644
|
+
name: "judge_eval_variant_result",
|
|
17645
|
+
description: "Per-variant grading result produced by a subagent of judge_eval_variant: scores against the shared rubric, composite, and a 1-3 sentence verdict for a single variant.",
|
|
17646
|
+
parametersSchema: JudgeEvalVariantResult
|
|
17647
|
+
}]);
|
|
17099
17648
|
try {
|
|
17100
17649
|
const executeTask = createPiTaskExecutor({
|
|
17101
17650
|
agentName: common.agent,
|
|
17651
|
+
subagentContractRegistry,
|
|
17102
17652
|
mountPath: sandbox.rootDir,
|
|
17103
17653
|
provider: common.provider,
|
|
17104
17654
|
model: common.model,
|
|
17105
17655
|
sandboxConfig: sandbox.config,
|
|
17656
|
+
makeExecutionPlan: (claimedTask) => executionPlans.getOrCreate(claimedTask),
|
|
17106
17657
|
makeOnTurnEvent: makeTurnEventHandlerFactory(rootLogger),
|
|
17107
17658
|
maxTurns: common.maxTurns,
|
|
17108
17659
|
maxBashTimeouts: common.maxBashTimeouts
|
|
@@ -17141,6 +17692,34 @@ async function runPolling(opts) {
|
|
|
17141
17692
|
log: (msg, err) => rootLogger.warn({ err }, msg)
|
|
17142
17693
|
}),
|
|
17143
17694
|
executeTask: async (claimedTask, reporter) => {
|
|
17695
|
+
const executionPlan = executionPlans.getOrCreate(claimedTask);
|
|
17696
|
+
const sessionDescriptor = executionPlan.descriptor;
|
|
17697
|
+
let expired;
|
|
17698
|
+
try {
|
|
17699
|
+
expired = slotRegistry.reapExpiredSlots();
|
|
17700
|
+
if (expired.length > 0) rootLogger.info({
|
|
17701
|
+
expiredCount: expired.length,
|
|
17702
|
+
slotKeys: expired.map((item) => item.slot.slotKey)
|
|
17703
|
+
}, "agent-daemon.daemon_slots_reaped");
|
|
17704
|
+
} catch (err) {
|
|
17705
|
+
rootLogger.error({
|
|
17706
|
+
phase: "daemon_slot_reap",
|
|
17707
|
+
err: err instanceof Error ? err.message : String(err)
|
|
17708
|
+
}, "agent-daemon.daemon_slot_reap_failed");
|
|
17709
|
+
}
|
|
17710
|
+
rootLogger.debug({
|
|
17711
|
+
taskId: claimedTask.task.id,
|
|
17712
|
+
taskType: claimedTask.task.taskType,
|
|
17713
|
+
resumable: sessionDescriptor.policy.resumable,
|
|
17714
|
+
workspaceMode: sessionDescriptor.policy.workspaceMode,
|
|
17715
|
+
workspaceScope: sessionDescriptor.policy.workspaceScope,
|
|
17716
|
+
sessionScope: sessionDescriptor.policy.sessionScope,
|
|
17717
|
+
slotKey: executionPlan.slotKey,
|
|
17718
|
+
slotId: executionPlan.slotId,
|
|
17719
|
+
sessionKey: executionPlan.sessionKey,
|
|
17720
|
+
piSessionDir: executionPlan.sessionPersistence?.sessionDir ?? null,
|
|
17721
|
+
workspaceId: executionPlan.workspaceId
|
|
17722
|
+
}, "agent-daemon.task_execution_policy");
|
|
17144
17723
|
if (taskTypes.length > 0 && !taskTypes.includes(claimedTask.task.taskType)) return {
|
|
17145
17724
|
taskId: claimedTask.task.id,
|
|
17146
17725
|
attemptN: claimedTask.attemptN,
|
|
@@ -17175,7 +17754,25 @@ async function runPolling(opts) {
|
|
|
17175
17754
|
retryable: false
|
|
17176
17755
|
}
|
|
17177
17756
|
};
|
|
17178
|
-
|
|
17757
|
+
if (executionPlan.slotKey && executionPlan.sessionPersistence) slotRegistry.beginSlot({
|
|
17758
|
+
...slotIdentity,
|
|
17759
|
+
slotKey: executionPlan.slotKey,
|
|
17760
|
+
taskType: claimedTask.task.taskType,
|
|
17761
|
+
sessionDir: executionPlan.sessionPersistence.sessionDir,
|
|
17762
|
+
sessionPath: resolveLatestPiSessionPath(executionPlan.sessionPersistence.sessionDir),
|
|
17763
|
+
workspaceId: executionPlan.workspaceId,
|
|
17764
|
+
worktreePath: executionPlan.workspaceId ? resolveTaskWorktreePath(mainRepo, executionPlan.workspaceId) : null,
|
|
17765
|
+
worktreeBranch: executionPlan.worktreeBranch,
|
|
17766
|
+
lastTaskId: claimedTask.task.id,
|
|
17767
|
+
lastAttemptN: claimedTask.attemptN,
|
|
17768
|
+
ttlSec: common.warmSessionTtlSec
|
|
17769
|
+
});
|
|
17770
|
+
try {
|
|
17771
|
+
return await executeTask(claimedTask, reporter);
|
|
17772
|
+
} finally {
|
|
17773
|
+
executionPlans.delete(claimedTask);
|
|
17774
|
+
if (executionPlan.slotKey) slotRegistry.finishSlot(slotIdentity, executionPlan.slotKey, common.warmSessionTtlSec, executionPlan.sessionPersistence ? resolveLatestPiSessionPath(executionPlan.sessionPersistence.sessionDir) : null);
|
|
17775
|
+
}
|
|
17179
17776
|
}
|
|
17180
17777
|
});
|
|
17181
17778
|
const drained = await runtime.start();
|
|
@@ -17183,6 +17780,7 @@ async function runPolling(opts) {
|
|
|
17183
17780
|
rootLogger.info({ processed: drained.length }, "agent-daemon.drained");
|
|
17184
17781
|
return drained.some((o) => o.status !== "completed") ? 1 : 0;
|
|
17185
17782
|
} finally {
|
|
17783
|
+
slotRegistry.close();
|
|
17186
17784
|
await otelShutdown();
|
|
17187
17785
|
await shutdownLogger();
|
|
17188
17786
|
}
|
|
@@ -17243,6 +17841,19 @@ async function runOnce(argv) {
|
|
|
17243
17841
|
throw err;
|
|
17244
17842
|
}
|
|
17245
17843
|
const sandbox = resolveSandbox(process.cwd(), values.sandbox);
|
|
17844
|
+
const stateDirs = ensureDaemonStateDirs(sandbox.rootDir);
|
|
17845
|
+
const slotRegistry = new DaemonSlotRegistry(stateDirs.registryDbPath);
|
|
17846
|
+
const slotIdentity = {
|
|
17847
|
+
agentName: opts.agent,
|
|
17848
|
+
provider: opts.provider,
|
|
17849
|
+
model: opts.model
|
|
17850
|
+
};
|
|
17851
|
+
const mainRepo = findMainWorktree();
|
|
17852
|
+
const executionPlans = createExecutionPlanCache({
|
|
17853
|
+
stateDirs,
|
|
17854
|
+
slotIdentity,
|
|
17855
|
+
warmSessionTtlSec: opts.warmSessionTtlSec
|
|
17856
|
+
});
|
|
17246
17857
|
const ctx = await resolveAgentContext(opts.agent);
|
|
17247
17858
|
const cfg = loadConfig();
|
|
17248
17859
|
const otelShutdown = await initWorkerOtel({
|
|
@@ -17290,17 +17901,58 @@ async function runOnce(argv) {
|
|
|
17290
17901
|
process.on("SIGTERM", () => {
|
|
17291
17902
|
onSignal("SIGTERM");
|
|
17292
17903
|
});
|
|
17904
|
+
const subagentContractRegistry = createSubagentContractRegistry([{
|
|
17905
|
+
name: "judge_eval_variant_result",
|
|
17906
|
+
description: "Per-variant grading result produced by a subagent of judge_eval_variant: scores against the shared rubric, composite, and a 1-3 sentence verdict for a single variant.",
|
|
17907
|
+
parametersSchema: JudgeEvalVariantResult
|
|
17908
|
+
}]);
|
|
17293
17909
|
try {
|
|
17294
|
-
const
|
|
17910
|
+
const rawExecuteTask = createPiTaskExecutor({
|
|
17295
17911
|
agentName: opts.agent,
|
|
17912
|
+
subagentContractRegistry,
|
|
17296
17913
|
mountPath: sandbox.rootDir,
|
|
17297
17914
|
provider: opts.provider,
|
|
17298
17915
|
model: opts.model,
|
|
17299
17916
|
sandboxConfig: sandbox.config,
|
|
17917
|
+
makeExecutionPlan: (claimedTask) => executionPlans.getOrCreate(claimedTask),
|
|
17300
17918
|
onTurnEvent: makeTurnEventHandler(rootLogger, { taskId }),
|
|
17301
17919
|
maxTurns: opts.maxTurns,
|
|
17302
17920
|
maxBashTimeouts: opts.maxBashTimeouts
|
|
17303
17921
|
});
|
|
17922
|
+
const executeTask = async (claimedTask, reporter) => {
|
|
17923
|
+
try {
|
|
17924
|
+
const expired = slotRegistry.reapExpiredSlots();
|
|
17925
|
+
if (expired.length > 0) rootLogger.info({
|
|
17926
|
+
expiredCount: expired.length,
|
|
17927
|
+
slotKeys: expired.map((item) => item.slot.slotKey)
|
|
17928
|
+
}, "agent-daemon.daemon_slots_reaped");
|
|
17929
|
+
} catch (err) {
|
|
17930
|
+
rootLogger.error({
|
|
17931
|
+
phase: "daemon_slot_reap",
|
|
17932
|
+
err: err instanceof Error ? err.message : String(err)
|
|
17933
|
+
}, "agent-daemon.daemon_slot_reap_failed");
|
|
17934
|
+
}
|
|
17935
|
+
const executionPlan = executionPlans.getOrCreate(claimedTask);
|
|
17936
|
+
if (executionPlan.slotKey && executionPlan.sessionPersistence) slotRegistry.beginSlot({
|
|
17937
|
+
...slotIdentity,
|
|
17938
|
+
slotKey: executionPlan.slotKey,
|
|
17939
|
+
taskType: claimedTask.task.taskType,
|
|
17940
|
+
sessionDir: executionPlan.sessionPersistence.sessionDir,
|
|
17941
|
+
sessionPath: resolveLatestPiSessionPath(executionPlan.sessionPersistence.sessionDir),
|
|
17942
|
+
workspaceId: executionPlan.workspaceId,
|
|
17943
|
+
worktreePath: executionPlan.workspaceId ? resolveTaskWorktreePath(mainRepo, executionPlan.workspaceId) : null,
|
|
17944
|
+
worktreeBranch: executionPlan.worktreeBranch,
|
|
17945
|
+
lastTaskId: claimedTask.task.id,
|
|
17946
|
+
lastAttemptN: claimedTask.attemptN,
|
|
17947
|
+
ttlSec: opts.warmSessionTtlSec
|
|
17948
|
+
});
|
|
17949
|
+
try {
|
|
17950
|
+
return await rawExecuteTask(claimedTask, reporter);
|
|
17951
|
+
} finally {
|
|
17952
|
+
executionPlans.delete(claimedTask);
|
|
17953
|
+
if (executionPlan.slotKey) slotRegistry.finishSlot(slotIdentity, executionPlan.slotKey, opts.warmSessionTtlSec, executionPlan.sessionPersistence ? resolveLatestPiSessionPath(executionPlan.sessionPersistence.sessionDir) : null);
|
|
17954
|
+
}
|
|
17955
|
+
};
|
|
17304
17956
|
const writeCorrelationAnchors = makePrBodyAnchorWriter({
|
|
17305
17957
|
gh: createGhCliClient(),
|
|
17306
17958
|
logger: rootLogger
|
|
@@ -17335,6 +17987,7 @@ async function runOnce(argv) {
|
|
|
17335
17987
|
console.log(JSON.stringify(output, null, 2));
|
|
17336
17988
|
return output.status === "completed" ? 0 : 1;
|
|
17337
17989
|
} finally {
|
|
17990
|
+
slotRegistry.close();
|
|
17338
17991
|
await otelShutdown();
|
|
17339
17992
|
await shutdownLogger();
|
|
17340
17993
|
}
|