@themoltnet/agent-daemon 0.17.0 → 0.18.1
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 +19 -8
- package/dist/main.js +84 -21
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -50,8 +50,17 @@ The agent's `moltnet.json` and gitconfig live next to each other in
|
|
|
50
50
|
|
|
51
51
|
### Pi provider auth
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
The daemon resolves Pi config from the repository-local `.pi` directory by
|
|
54
|
+
default. On startup, if `PI_CODING_AGENT_DIR` is not already set, the daemon
|
|
55
|
+
sets it to `<repo-root>/.pi` before creating Pi sessions. This keeps daemon
|
|
56
|
+
runs deterministic and avoids inheriting user-level `~/.pi/agent` state.
|
|
57
|
+
|
|
58
|
+
Repo-local `.pi/settings.json` and `.pi/models.json` are intended to be
|
|
59
|
+
committed. `models.json` should reference provider keys by environment-variable
|
|
60
|
+
name, for example `"apiKey": "OLLAMA_API_KEY"`, not contain secret values.
|
|
61
|
+
Repo-local `.pi/auth.json` may exist for local subscription auth, but is
|
|
62
|
+
gitignored. Without `.pi/auth.json`, Pi falls back to environment-variable
|
|
63
|
+
provider keys:
|
|
55
64
|
|
|
56
65
|
```bash
|
|
57
66
|
export ANTHROPIC_API_KEY=sk-ant-...
|
|
@@ -59,7 +68,8 @@ export ANTHROPIC_API_KEY=sk-ant-...
|
|
|
59
68
|
# https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/env-api-keys.ts
|
|
60
69
|
```
|
|
61
70
|
|
|
62
|
-
To
|
|
71
|
+
To force a non-repo Pi directory, set
|
|
72
|
+
`PI_CODING_AGENT_DIR=/abs/path/to/.pi-or-agent-dir` before starting the daemon.
|
|
63
73
|
|
|
64
74
|
### Observability
|
|
65
75
|
|
|
@@ -126,11 +136,12 @@ registered task type; unknown task-type names remain invalid.
|
|
|
126
136
|
### Prerequisites
|
|
127
137
|
|
|
128
138
|
- Docker running.
|
|
129
|
-
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
139
|
+
- Pi config for the model provider you'll drive the daemon with. Local daemon
|
|
140
|
+
runs default `PI_CODING_AGENT_DIR` to repo-local `.pi`, so committed
|
|
141
|
+
`.pi/settings.json` and `.pi/models.json` must list the provider/model. For
|
|
142
|
+
subscription auth, put your local token blob in `.pi/auth.json`; it is
|
|
143
|
+
gitignored. For API-key auth, keep `.pi/auth.json` absent and export the
|
|
144
|
+
provider key referenced by `.pi/models.json`, for example `OLLAMA_API_KEY`.
|
|
134
145
|
- `ssh-keygen` on `PATH`.
|
|
135
146
|
- A `sandbox.json` at the repo root, or an explicit `--sandbox <path>` when
|
|
136
147
|
starting the daemon. The daemon searches up for this file and uses its
|
package/dist/main.js
CHANGED
|
@@ -31258,12 +31258,21 @@ if (!etc.sha512Sync) etc.sha512Sync = (...m) => {
|
|
|
31258
31258
|
*/
|
|
31259
31259
|
async function isContinuationClaimableByThisDaemon(task, slotRegistry) {
|
|
31260
31260
|
const cf = task.input?.continueFrom;
|
|
31261
|
-
if (!cf) return true;
|
|
31261
|
+
if (!cf) return { claimable: true };
|
|
31262
31262
|
const slot = await slotRegistry.findLatestProducerSlotByTaskAttempt(cf.taskId, cf.attemptN);
|
|
31263
|
-
if (!slot) return
|
|
31263
|
+
if (!slot) return {
|
|
31264
|
+
claimable: false,
|
|
31265
|
+
reason: "missing_producer_slot",
|
|
31266
|
+
continueFrom: cf
|
|
31267
|
+
};
|
|
31264
31268
|
const sessionDir = slot.session?.sessionDir;
|
|
31265
|
-
if (!sessionDir || !existsSync(sessionDir)) return
|
|
31266
|
-
|
|
31269
|
+
if (!sessionDir || !existsSync(sessionDir)) return {
|
|
31270
|
+
claimable: false,
|
|
31271
|
+
reason: "missing_session_dir",
|
|
31272
|
+
continueFrom: cf,
|
|
31273
|
+
sessionDir
|
|
31274
|
+
};
|
|
31275
|
+
return { claimable: true };
|
|
31267
31276
|
}
|
|
31268
31277
|
var DEFAULT_LIST_LIMIT = 10;
|
|
31269
31278
|
var DEFAULT_POLL_INTERVAL_MS = 2e3;
|
|
@@ -31351,7 +31360,19 @@ var PollingApiTaskSource = class {
|
|
|
31351
31360
|
if (seen.has(item.id)) continue;
|
|
31352
31361
|
if (this.opts.taskTypes && this.opts.taskTypes.length > 0 && !this.opts.taskTypes.includes(item.taskType)) continue;
|
|
31353
31362
|
if (this.opts.diaryIds && this.opts.diaryIds.length > 0 && (item.diaryId === null || !this.opts.diaryIds.includes(item.diaryId))) continue;
|
|
31354
|
-
if (this.opts.slotRegistry
|
|
31363
|
+
if (this.opts.slotRegistry) {
|
|
31364
|
+
const affinity = await isContinuationClaimableByThisDaemon(item, this.opts.slotRegistry);
|
|
31365
|
+
if (!affinity.claimable) {
|
|
31366
|
+
this.logger.debug({
|
|
31367
|
+
taskId: item.id,
|
|
31368
|
+
taskType: item.taskType,
|
|
31369
|
+
reason: affinity.reason,
|
|
31370
|
+
continueFrom: affinity.continueFrom,
|
|
31371
|
+
sessionDir: affinity.sessionDir
|
|
31372
|
+
}, "polling-api.continuation_skipped");
|
|
31373
|
+
continue;
|
|
31374
|
+
}
|
|
31375
|
+
}
|
|
31355
31376
|
if (this.opts.profileId) {
|
|
31356
31377
|
const allowed = item.allowedProfiles ?? [];
|
|
31357
31378
|
if (allowed.length > 0 && !allowed.some((p) => p.profileId === this.opts.profileId)) continue;
|
|
@@ -32824,8 +32845,7 @@ async function resumeVm(config) {
|
|
|
32824
32845
|
signal: config.signal
|
|
32825
32846
|
});
|
|
32826
32847
|
if (creds.gitconfig) {
|
|
32827
|
-
const
|
|
32828
|
-
const vmGitconfig = creds.gitconfig.replace(/signingKey\s*=\s*.+/g, `signingKey = ${vmSigningKey}`);
|
|
32848
|
+
const vmGitconfig = rewriteGitconfigPaths(creds.gitconfig, vmSshDir, vmAgentDir);
|
|
32829
32849
|
await vm.fs.writeFile(`${vmAgentDir}/gitconfig`, vmGitconfig, {
|
|
32830
32850
|
mode: 420,
|
|
32831
32851
|
signal: config.signal
|
|
@@ -32848,17 +32868,6 @@ async function resumeVm(config) {
|
|
|
32848
32868
|
signal: config.signal
|
|
32849
32869
|
});
|
|
32850
32870
|
await vm.exec("chown -R agent:agent /home/agent/.pi /home/agent/.moltnet", { signal: config.signal });
|
|
32851
|
-
const gitCredHelperPath = `${vmSshDir}/git-credential-moltnet`;
|
|
32852
|
-
const credHelperScript = `#!/bin/sh
|
|
32853
|
-
echo "username=x-access-token"
|
|
32854
|
-
echo "password=$(moltnet github token --credentials ${vmSshDir}/moltnet.json)"
|
|
32855
|
-
`;
|
|
32856
|
-
await vm.fs.writeFile(gitCredHelperPath, credHelperScript, {
|
|
32857
|
-
mode: 493,
|
|
32858
|
-
signal: config.signal
|
|
32859
|
-
});
|
|
32860
|
-
await vmRun(vm, "git credential helper", `git config --global credential.helper ${gitCredHelperPath} && \
|
|
32861
|
-
git config --global url."https://github.com/".insteadOf "git@github.com:"`, config.signal);
|
|
32862
32871
|
return {
|
|
32863
32872
|
vm,
|
|
32864
32873
|
credentials: creds,
|
|
@@ -32877,6 +32886,30 @@ echo "password=$(moltnet github token --credentials ${vmSshDir}/moltnet.json)"
|
|
|
32877
32886
|
}
|
|
32878
32887
|
}
|
|
32879
32888
|
/**
|
|
32889
|
+
* Rewrite host-absolute paths inside an agent gitconfig to VM-local
|
|
32890
|
+
* equivalents before injecting it into the guest.
|
|
32891
|
+
*
|
|
32892
|
+
* Two rewrites:
|
|
32893
|
+
* - `signingKey = <host path>` → `<vmSshDir>/id_ed25519`
|
|
32894
|
+
* - `... credential-helper --credentials <host moltnet.json>`
|
|
32895
|
+
* → `<vmAgentDir>/moltnet.json`
|
|
32896
|
+
*
|
|
32897
|
+
* The credential-helper line is generated host-side by `moltnet github setup`
|
|
32898
|
+
* with a host-absolute `--credentials` path; inside the guest that path is
|
|
32899
|
+
* invalid, so it must point at the VM-side moltnet.json. The `insteadOf`
|
|
32900
|
+
* rewrite rule and every other line are workspace-independent and pass through
|
|
32901
|
+
* unchanged. A gitconfig without a credential helper is rewritten only for
|
|
32902
|
+
* `signingKey`.
|
|
32903
|
+
*
|
|
32904
|
+
* This is the single source of truth for git push auth in the guest: the
|
|
32905
|
+
* injected gitconfig carries the tokenless mint-on-demand helper, so the VM
|
|
32906
|
+
* no longer hand-rolls a credential-helper script or runs an imperative
|
|
32907
|
+
* `git config --global ... insteadOf` against the guest $HOME.
|
|
32908
|
+
*/
|
|
32909
|
+
function rewriteGitconfigPaths(gitconfig, vmSshDir, vmAgentDir) {
|
|
32910
|
+
return gitconfig.replace(/signingKey\s*=\s*.+/g, `signingKey = ${vmSshDir}/id_ed25519`).replace(/(moltnet github credential-helper --credentials )\S+/g, `$1${vmAgentDir}/moltnet.json`);
|
|
32911
|
+
}
|
|
32912
|
+
/**
|
|
32880
32913
|
* Rewrite host-absolute paths inside moltnet.json to VM-local equivalents.
|
|
32881
32914
|
*
|
|
32882
32915
|
* Fields rewritten:
|
|
@@ -34785,9 +34818,13 @@ function loadConfig() {
|
|
|
34785
34818
|
otelEndpoint: process.env["MOLTNET_OTEL_ENDPOINT"] ?? "",
|
|
34786
34819
|
logLevel: process.env["LOG_LEVEL"] ?? "",
|
|
34787
34820
|
profilePrerequisiteEnv: process.env,
|
|
34788
|
-
profilePrerequisitePath: process.env.PATH ?? ""
|
|
34821
|
+
profilePrerequisitePath: process.env.PATH ?? "",
|
|
34822
|
+
piCodingAgentDir: process.env["PI_CODING_AGENT_DIR"] ?? ""
|
|
34789
34823
|
};
|
|
34790
34824
|
}
|
|
34825
|
+
function activatePiCodingAgentDir(path) {
|
|
34826
|
+
process.env["PI_CODING_AGENT_DIR"] = path;
|
|
34827
|
+
}
|
|
34791
34828
|
//#endregion
|
|
34792
34829
|
//#region src/lib/agent-context.ts
|
|
34793
34830
|
/**
|
|
@@ -35052,6 +35089,7 @@ async function maybeAttachWarmSlotContext(claimedTask, basePlan, stateDirs, slot
|
|
|
35052
35089
|
return {
|
|
35053
35090
|
...basePlan,
|
|
35054
35091
|
workspaceMode: "dedicated_worktree",
|
|
35092
|
+
workspaceId: resolution.producerSlot.workspace?.workspaceId ?? null,
|
|
35055
35093
|
worktreeBranch: resolution.producerSlot.workspace?.worktreeBranch ?? null,
|
|
35056
35094
|
sessionPersistence: {
|
|
35057
35095
|
sessionDir: `${stateDirs.piSessionsDir}/continue-${claimedTask.task.id}-attempt-${claimedTask.attemptN}`,
|
|
@@ -35374,6 +35412,23 @@ async function initWorkerOtel(options) {
|
|
|
35374
35412
|
};
|
|
35375
35413
|
}
|
|
35376
35414
|
//#endregion
|
|
35415
|
+
//#region src/lib/pi-agent-dir.ts
|
|
35416
|
+
function ensurePiAgentDir(repoRoot, explicitPath) {
|
|
35417
|
+
if (explicitPath) {
|
|
35418
|
+
mkdirSync(explicitPath, { recursive: true });
|
|
35419
|
+
return {
|
|
35420
|
+
path: explicitPath,
|
|
35421
|
+
source: "env"
|
|
35422
|
+
};
|
|
35423
|
+
}
|
|
35424
|
+
const path = join(repoRoot, ".pi");
|
|
35425
|
+
mkdirSync(path, { recursive: true });
|
|
35426
|
+
return {
|
|
35427
|
+
path,
|
|
35428
|
+
source: "repo"
|
|
35429
|
+
};
|
|
35430
|
+
}
|
|
35431
|
+
//#endregion
|
|
35377
35432
|
//#region src/lib/runtime-profile.ts
|
|
35378
35433
|
var RuntimeProfilePrerequisiteError = class extends Error {
|
|
35379
35434
|
constructor(profileName, missingEnv, missingTools) {
|
|
@@ -35614,6 +35669,8 @@ async function runPolling(opts) {
|
|
|
35614
35669
|
rootDir: profile.mountPath,
|
|
35615
35670
|
path: profile.source
|
|
35616
35671
|
} : resolveSandbox(process.cwd(), values.sandbox);
|
|
35672
|
+
const piAgentDir = ensurePiAgentDir(sandbox.rootDir, cfg.piCodingAgentDir);
|
|
35673
|
+
activatePiCodingAgentDir(piAgentDir.path);
|
|
35617
35674
|
const stateDirs = ensureDaemonStateDirs(sandbox.rootDir);
|
|
35618
35675
|
const slotRegistry = new DaemonSlotRegistry(resolveDaemonStateStorageConfig(stateDirs.registryDbPath, cfg.agentDaemonStateDatabaseUrl));
|
|
35619
35676
|
const slotIdentity = {
|
|
@@ -35691,7 +35748,9 @@ async function runPolling(opts) {
|
|
|
35691
35748
|
profileId: profile.id,
|
|
35692
35749
|
profileSessionTtlSec: profile.sessionTtlSec,
|
|
35693
35750
|
profileWorkspaceTtlSec: profile.workspaceTtlSec
|
|
35694
|
-
} : {}
|
|
35751
|
+
} : {},
|
|
35752
|
+
piAgentDir: piAgentDir.path,
|
|
35753
|
+
piAgentDirSource: piAgentDir.source
|
|
35695
35754
|
}, "agent-daemon.starting");
|
|
35696
35755
|
const outputs = [];
|
|
35697
35756
|
try {
|
|
@@ -35967,6 +36026,8 @@ async function runOnce(argv) {
|
|
|
35967
36026
|
rootDir: profile.mountPath,
|
|
35968
36027
|
path: profile.source
|
|
35969
36028
|
} : resolveSandbox(process.cwd(), values.sandbox);
|
|
36029
|
+
const piAgentDir = ensurePiAgentDir(sandbox.rootDir, cfg.piCodingAgentDir);
|
|
36030
|
+
activatePiCodingAgentDir(piAgentDir.path);
|
|
35970
36031
|
const stateDirs = ensureDaemonStateDirs(sandbox.rootDir);
|
|
35971
36032
|
const slotRegistry = new DaemonSlotRegistry(resolveDaemonStateStorageConfig(stateDirs.registryDbPath, cfg.agentDaemonStateDatabaseUrl));
|
|
35972
36033
|
const slotIdentity = {
|
|
@@ -36017,7 +36078,9 @@ async function runOnce(argv) {
|
|
|
36017
36078
|
profileId: profile.id,
|
|
36018
36079
|
profileSessionTtlSec: profile.sessionTtlSec,
|
|
36019
36080
|
profileWorkspaceTtlSec: profile.workspaceTtlSec
|
|
36020
|
-
} : {}
|
|
36081
|
+
} : {},
|
|
36082
|
+
piAgentDir: piAgentDir.path,
|
|
36083
|
+
piAgentDirSource: piAgentDir.source
|
|
36021
36084
|
}, "agent-daemon.starting");
|
|
36022
36085
|
let runtime = null;
|
|
36023
36086
|
let activeAttemptN = null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@themoltnet/agent-daemon",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.1",
|
|
4
4
|
"license": "AGPL-3.0-only",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "MoltNet agent daemon — claims and executes tasks (fulfill_brief, assess_brief) from the MoltNet task-service via Pi-headless. CLI: moltnet-agent.",
|
|
@@ -45,10 +45,10 @@
|
|
|
45
45
|
"@opentelemetry/semantic-conventions": "^1.39.0",
|
|
46
46
|
"pino": "^10.3.1",
|
|
47
47
|
"pino-pretty": "^13.1.3",
|
|
48
|
-
"@themoltnet/agent-
|
|
48
|
+
"@themoltnet/agent-runtime": "0.25.0",
|
|
49
49
|
"@themoltnet/sdk": "0.108.0",
|
|
50
|
-
"@themoltnet/
|
|
51
|
-
"@themoltnet/
|
|
50
|
+
"@themoltnet/pi-extension": "0.24.1",
|
|
51
|
+
"@themoltnet/agent-daemon-state": "0.2.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"tsx": "^4.7.0",
|
|
@@ -57,8 +57,8 @@
|
|
|
57
57
|
"vitest": "^3.0.0",
|
|
58
58
|
"@moltnet/bootstrap": "0.1.0",
|
|
59
59
|
"@moltnet/crypto-service": "0.1.0",
|
|
60
|
-
"@moltnet/
|
|
61
|
-
"@moltnet/
|
|
60
|
+
"@moltnet/tasks": "0.1.0",
|
|
61
|
+
"@moltnet/observability": "0.1.0"
|
|
62
62
|
},
|
|
63
63
|
"nx": {
|
|
64
64
|
"tags": [
|