@themoltnet/agent-daemon 0.5.3 → 0.6.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/dist/main.js +200 -59
- package/package.json +5 -5
package/dist/main.js
CHANGED
|
@@ -3,7 +3,7 @@ import crypto, { createHash } from "crypto";
|
|
|
3
3
|
import { parseArgs, parseEnv, promisify } from "node:util";
|
|
4
4
|
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync } from "node:fs";
|
|
5
5
|
import { ROOT_CONTEXT, SpanStatusCode, context, metrics, propagation, trace } from "@opentelemetry/api";
|
|
6
|
-
import { pino } from "pino";
|
|
6
|
+
import { pino, transport } from "pino";
|
|
7
7
|
import { readFile } from "node:fs/promises";
|
|
8
8
|
import { createHash as createHash$1 } from "node:crypto";
|
|
9
9
|
import path, { dirname, isAbsolute, join, resolve } from "node:path";
|
|
@@ -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 { once } from "node:events";
|
|
15
16
|
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
|
|
16
17
|
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
17
18
|
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
@@ -5990,6 +5991,14 @@ var COMMON_OPTIONAL_FLAGS = `\
|
|
|
5990
5991
|
--heartbeat-interval-ms <n> Reporter heartbeat cadence. Default: 60000.
|
|
5991
5992
|
--max-batch-size <n> Reporter message batch size. Default: 50.
|
|
5992
5993
|
--flush-interval-ms <n> Reporter flush window. Default: 200.
|
|
5994
|
+
--max-turns <n> Cap on tool-use turns per attempt; over this,
|
|
5995
|
+
the pi session aborts and the attempt fails
|
|
5996
|
+
with code 'max_turns_exceeded'. 0 = disabled.
|
|
5997
|
+
Default: 0. Recommended ~30 for fulfill_brief.
|
|
5998
|
+
--max-bash-timeouts <n> Cap on bash-tool timeouts per attempt; over
|
|
5999
|
+
this, the pi session aborts and the attempt
|
|
6000
|
+
fails with code 'max_bash_timeouts_exceeded'.
|
|
6001
|
+
0 = disabled. Default: 3.
|
|
5993
6002
|
--debug Verbose logging: also log successful list/claim
|
|
5994
6003
|
outcomes (candidate counts, claim attempts).`;
|
|
5995
6004
|
var REGISTERED_TASK_TYPES = Object.keys(BUILT_IN_TASK_TYPES).sort();
|
|
@@ -14344,7 +14353,7 @@ function createMoltNetTools(config) {
|
|
|
14344
14353
|
const createEntry = defineTool({
|
|
14345
14354
|
name: "moltnet_create_entry",
|
|
14346
14355
|
label: "Create MoltNet Diary Entry",
|
|
14347
|
-
description: "Create a new diary entry to record decisions, findings, incidents, or reflections. During an active task, the entry is forced into the task diary and tagged with the task:* provenance namespace (task:id:<id>, task:type:<type>, task:attempt:<n>, plus task:correlation:<id> when set); an explicit diaryId mismatching the task diary is rejected.",
|
|
14356
|
+
description: "Create a new diary entry to record decisions, findings, incidents, or reflections. During an active task, the entry is forced into the task diary and tagged with the task:* provenance namespace (task:id:<id>, task:type:<type>, task:attempt:<n>, plus task:correlation:<id> when set); an explicit diaryId mismatching the task diary is rejected. Use this tool — NOT `moltnet entry create` / `moltnet entry create-signed` via bash. The CLI path bypasses task-tag auto-injection and leaves entries invisible to taskFilter queries.",
|
|
14348
14357
|
parameters: Type.Object({
|
|
14349
14358
|
title: Type.String({ description: "Entry title (concise, descriptive)" }),
|
|
14350
14359
|
content: Type.String({ description: "Entry content (markdown)" }),
|
|
@@ -14924,38 +14933,48 @@ async function resumeVm(config) {
|
|
|
14924
14933
|
[GUEST_TASK_SKILLS_MOUNT]: new MemoryProvider()
|
|
14925
14934
|
} }
|
|
14926
14935
|
});
|
|
14927
|
-
|
|
14936
|
+
try {
|
|
14937
|
+
await vm.exec(`sh -c '
|
|
14928
14938
|
cp /etc/gondolin/mitm/ca.crt /usr/local/share/ca-certificates/gondolin-mitm.crt
|
|
14929
14939
|
update-ca-certificates 2>/dev/null
|
|
14930
14940
|
cat /etc/gondolin/mitm/ca.crt >> /etc/ssl/certs/ca-certificates.crt
|
|
14931
14941
|
'`);
|
|
14932
|
-
|
|
14933
|
-
|
|
14934
|
-
|
|
14935
|
-
|
|
14936
|
-
|
|
14937
|
-
|
|
14938
|
-
|
|
14939
|
-
|
|
14940
|
-
|
|
14941
|
-
|
|
14942
|
-
|
|
14943
|
-
|
|
14944
|
-
|
|
14945
|
-
|
|
14946
|
-
|
|
14947
|
-
|
|
14948
|
-
|
|
14949
|
-
|
|
14950
|
-
|
|
14951
|
-
|
|
14952
|
-
|
|
14953
|
-
|
|
14954
|
-
|
|
14955
|
-
|
|
14956
|
-
|
|
14957
|
-
|
|
14958
|
-
|
|
14942
|
+
await vmRun(vm, "DNS resolvers", `printf 'nameserver 8.8.8.8\\nnameserver 1.1.1.1\\n' > /etc/resolv.conf`);
|
|
14943
|
+
await vmRun(vm, "git safe.directory", `git config --system --add safe.directory '*'`);
|
|
14944
|
+
for (const [i, cmd] of (config.sandboxConfig?.resumeCommands ?? []).entries()) await vmRun(vm, `resumeCommands[${i}]`, cmd);
|
|
14945
|
+
const vmSshDir = `${vmAgentDir}/ssh`;
|
|
14946
|
+
await vm.exec(`mkdir -p ${vmAgentDir}/ssh /home/agent/.pi/agent`);
|
|
14947
|
+
if (creds.piAuthJson !== null) await vm.fs.writeFile("/home/agent/.pi/agent/auth.json", creds.piAuthJson, { mode: 384 });
|
|
14948
|
+
const vmMoltnetJson = rewriteMoltnetJsonPaths(creds.moltnetJson, vmAgentDir, vmSshDir, creds.githubAppPemFilename);
|
|
14949
|
+
await vm.fs.writeFile(`${vmAgentDir}/moltnet.json`, vmMoltnetJson, { mode: 384 });
|
|
14950
|
+
await vm.fs.writeFile(`${vmAgentDir}/env`, creds.agentEnvRaw, { mode: 384 });
|
|
14951
|
+
if (creds.gitconfig) {
|
|
14952
|
+
const vmSigningKey = `${vmSshDir}/id_ed25519`;
|
|
14953
|
+
let vmGitconfig = creds.gitconfig.replace(/signingKey\s*=\s*.+/g, `signingKey = ${vmSigningKey}`);
|
|
14954
|
+
vmGitconfig = ensureRelativeWorktreePaths(vmGitconfig);
|
|
14955
|
+
await vm.fs.writeFile(`${vmAgentDir}/gitconfig`, vmGitconfig, { mode: 420 });
|
|
14956
|
+
}
|
|
14957
|
+
if (creds.sshPrivateKey) await vm.fs.writeFile(`${vmSshDir}/id_ed25519`, creds.sshPrivateKey, { mode: 384 });
|
|
14958
|
+
if (creds.sshPublicKey) await vm.fs.writeFile(`${vmSshDir}/id_ed25519.pub`, creds.sshPublicKey, { mode: 420 });
|
|
14959
|
+
if (creds.allowedSigners) await vm.fs.writeFile(`${vmSshDir}/allowed_signers`, creds.allowedSigners, { mode: 420 });
|
|
14960
|
+
if (creds.githubAppPem && creds.githubAppPemFilename) await vm.fs.writeFile(`${vmAgentDir}/${creds.githubAppPemFilename}`, creds.githubAppPem, { mode: 384 });
|
|
14961
|
+
await vm.exec("chown -R agent:agent /home/agent/.pi /home/agent/.moltnet");
|
|
14962
|
+
return {
|
|
14963
|
+
vm,
|
|
14964
|
+
credentials: creds,
|
|
14965
|
+
mountPath: config.mountPath,
|
|
14966
|
+
guestWorkspace: GUEST_WORKSPACE$1,
|
|
14967
|
+
agentDir
|
|
14968
|
+
};
|
|
14969
|
+
} catch (err) {
|
|
14970
|
+
try {
|
|
14971
|
+
await vm.close();
|
|
14972
|
+
} catch (closeErr) {
|
|
14973
|
+
const m = closeErr instanceof Error ? closeErr.message : String(closeErr);
|
|
14974
|
+
process.stderr.write(`[vm] post-throw vm.close() failed: ${m}\n`);
|
|
14975
|
+
}
|
|
14976
|
+
throw err;
|
|
14977
|
+
}
|
|
14959
14978
|
}
|
|
14960
14979
|
/**
|
|
14961
14980
|
* Rewrite host-absolute paths inside moltnet.json to VM-local equivalents.
|
|
@@ -15464,8 +15483,8 @@ function buildRuntimeInstructor(ctx) {
|
|
|
15464
15483
|
"## Diary discipline",
|
|
15465
15484
|
"",
|
|
15466
15485
|
`- During this task, every diary entry MUST land in \`${ctx.diaryId}\``,
|
|
15467
|
-
" (the task diary). The
|
|
15468
|
-
" and rejects mismatched explicit `diaryId` parameters.",
|
|
15486
|
+
" (the task diary). The `moltnet_create_entry` custom tool enforces",
|
|
15487
|
+
" this and rejects mismatched explicit `diaryId` parameters.",
|
|
15469
15488
|
`- Provenance tags \`task:id:${ctx.taskId}\`, \`task:type:${ctx.taskType}\`,`,
|
|
15470
15489
|
` and \`task:attempt:${ctx.attemptN}\`${ctx.correlationId ? `, plus \`task:correlation:${ctx.correlationId}\`` : ""} are auto-injected on every entry.`,
|
|
15471
15490
|
" These share the `task:` namespace so `moltnet_diary_tags` with",
|
|
@@ -15473,12 +15492,23 @@ function buildRuntimeInstructor(ctx) {
|
|
|
15473
15492
|
" `taskFilter` shorthand on `moltnet_list_entries` /",
|
|
15474
15493
|
" `moltnet_search_entries` expands into them. You may add additional",
|
|
15475
15494
|
" tags but you cannot remove the auto-injected ones.",
|
|
15495
|
+
"- **DO NOT shell out to `moltnet entry create` / `moltnet entry",
|
|
15496
|
+
" create-signed` / any other `moltnet entry` subcommand via bash.**",
|
|
15497
|
+
" Those CLI paths hit the REST API directly and bypass the",
|
|
15498
|
+
" custom tool's task-tag auto-injection, leaving you with",
|
|
15499
|
+
" untagged entries that `moltnet_list_entries` with a",
|
|
15500
|
+
" `taskFilter: { taskId: ... }` cannot find. The legreffier skill",
|
|
15501
|
+
" recommends `moltnet entry *` for normal interactive sessions —",
|
|
15502
|
+
" inside a running task that advice does not apply. Use the",
|
|
15503
|
+
" `moltnet_create_entry` custom tool only.",
|
|
15476
15504
|
"",
|
|
15477
15505
|
"## Accountable commits",
|
|
15478
15506
|
"",
|
|
15479
15507
|
"- Every commit you make during this task MUST be paired with a signed",
|
|
15480
|
-
" diary entry created via `moltnet_create_entry
|
|
15481
|
-
" entry
|
|
15508
|
+
" diary entry created via the `moltnet_create_entry` custom tool",
|
|
15509
|
+
" (NOT via `moltnet entry create-signed` from bash — see Diary",
|
|
15510
|
+
" discipline above). Embed the returned entry id in the commit",
|
|
15511
|
+
" trailer `MoltNet-Diary: <id>`.",
|
|
15482
15512
|
"- Commits must be signed with the agent credentials (gitconfig is",
|
|
15483
15513
|
" pre-configured). Do not bypass signing.",
|
|
15484
15514
|
"",
|
|
@@ -16175,6 +16205,11 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
16175
16205
|
let assistantText = "";
|
|
16176
16206
|
let reporterError = null;
|
|
16177
16207
|
const usage = finalUsage;
|
|
16208
|
+
let capAbort = null;
|
|
16209
|
+
let toolUseTurnCount = 0;
|
|
16210
|
+
let bashTimeoutCount = 0;
|
|
16211
|
+
const maxTurns = opts.maxTurns ?? 0;
|
|
16212
|
+
const maxBashTimeouts = opts.maxBashTimeouts ?? 3;
|
|
16178
16213
|
cancelListener = wireSessionAbort(reporter.cancelSignal, session);
|
|
16179
16214
|
const recordingPromise = [];
|
|
16180
16215
|
const track = (p) => {
|
|
@@ -16189,6 +16224,23 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
16189
16224
|
}
|
|
16190
16225
|
}));
|
|
16191
16226
|
};
|
|
16227
|
+
const liveSession = session;
|
|
16228
|
+
const triggerCapAbort = (code, message) => {
|
|
16229
|
+
if (capAbort) return;
|
|
16230
|
+
capAbort = {
|
|
16231
|
+
code,
|
|
16232
|
+
message
|
|
16233
|
+
};
|
|
16234
|
+
liveSession.abort().catch((err) => {
|
|
16235
|
+
const m = err instanceof Error ? err.message : String(err);
|
|
16236
|
+
process.stderr.write(`[cap] session.abort() failed: ${m}\n`);
|
|
16237
|
+
});
|
|
16238
|
+
track(emit("info", {
|
|
16239
|
+
event: "cap_abort",
|
|
16240
|
+
code,
|
|
16241
|
+
message
|
|
16242
|
+
}));
|
|
16243
|
+
};
|
|
16192
16244
|
session.subscribe((event) => {
|
|
16193
16245
|
if (event.type === "message_update") {
|
|
16194
16246
|
const ae = event.assistantMessageEvent;
|
|
@@ -16197,12 +16249,17 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
16197
16249
|
track(emit("text_delta", { delta: ae.delta }));
|
|
16198
16250
|
}
|
|
16199
16251
|
} else if (event.type === "tool_execution_start") track(emit("tool_call_start", { tool_name: event.toolName }));
|
|
16200
|
-
else if (event.type === "tool_execution_end")
|
|
16201
|
-
|
|
16202
|
-
|
|
16203
|
-
|
|
16204
|
-
|
|
16205
|
-
|
|
16252
|
+
else if (event.type === "tool_execution_end") {
|
|
16253
|
+
track(emit("tool_call_end", {
|
|
16254
|
+
tool_name: event.toolName,
|
|
16255
|
+
is_error: event.isError,
|
|
16256
|
+
result: event.isError ? truncateForWire(event.result) : void 0
|
|
16257
|
+
}));
|
|
16258
|
+
if (maxBashTimeouts > 0 && event.toolName === "bash" && event.isError && isBashTimeoutResult(event.result)) {
|
|
16259
|
+
bashTimeoutCount += 1;
|
|
16260
|
+
if (bashTimeoutCount >= maxBashTimeouts) triggerCapAbort("max_bash_timeouts_exceeded", `Aborted after ${bashTimeoutCount} bash timeouts in this attempt (cap ${maxBashTimeouts}).`);
|
|
16261
|
+
}
|
|
16262
|
+
} else if (event.type === "turn_end") {
|
|
16206
16263
|
const msg = event.message;
|
|
16207
16264
|
if (msg?.role === "assistant" && msg.usage) {
|
|
16208
16265
|
usage.inputTokens += Math.max(0, msg.usage.input ?? 0);
|
|
@@ -16212,7 +16269,12 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
16212
16269
|
if (cr) usage.cacheReadTokens = (usage.cacheReadTokens ?? 0) + cr;
|
|
16213
16270
|
if (cw) usage.cacheWriteTokens = (usage.cacheWriteTokens ?? 0) + cw;
|
|
16214
16271
|
}
|
|
16215
|
-
|
|
16272
|
+
const stopReason = msg?.stopReason ?? "end_turn";
|
|
16273
|
+
track(emit("turn_end", { stop_reason: stopReason }));
|
|
16274
|
+
if (maxTurns > 0 && stopReason !== "end_turn" && stopReason !== "aborted" && stopReason !== "error") {
|
|
16275
|
+
toolUseTurnCount += 1;
|
|
16276
|
+
if (toolUseTurnCount >= maxTurns) triggerCapAbort("max_turns_exceeded", `Aborted after ${toolUseTurnCount} tool-use turns (cap ${maxTurns}).`);
|
|
16277
|
+
}
|
|
16216
16278
|
llmAbort = msg?.stopReason === "error";
|
|
16217
16279
|
if (msg?.stopReason === "error") llmErrorMessage = typeof msg.errorMessage === "string" && msg.errorMessage.length > 0 ? msg.errorMessage : null;
|
|
16218
16280
|
else llmErrorMessage = null;
|
|
@@ -16241,7 +16303,7 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
16241
16303
|
let parsedOutput = null;
|
|
16242
16304
|
let parsedOutputCid = null;
|
|
16243
16305
|
let parseError = null;
|
|
16244
|
-
if (!runError && !llmAbort && !cancelled) {
|
|
16306
|
+
if (!runError && !llmAbort && !cancelled && !capAbort) {
|
|
16245
16307
|
const captured = submitToolHandle?.getCaptured() ?? null;
|
|
16246
16308
|
if (captured) try {
|
|
16247
16309
|
parsedOutput = captured;
|
|
@@ -16294,6 +16356,21 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
16294
16356
|
retryable: false
|
|
16295
16357
|
}
|
|
16296
16358
|
};
|
|
16359
|
+
const capAbortSnapshot = capAbort;
|
|
16360
|
+
if (capAbortSnapshot) return {
|
|
16361
|
+
taskId: task.id,
|
|
16362
|
+
attemptN,
|
|
16363
|
+
status: "failed",
|
|
16364
|
+
output: null,
|
|
16365
|
+
outputCid: null,
|
|
16366
|
+
usage,
|
|
16367
|
+
durationMs: Date.now() - startTime,
|
|
16368
|
+
error: {
|
|
16369
|
+
code: capAbortSnapshot.code,
|
|
16370
|
+
message: capAbortSnapshot.message,
|
|
16371
|
+
retryable: false
|
|
16372
|
+
}
|
|
16373
|
+
};
|
|
16297
16374
|
const status = runError || llmAbort || parseError || reporterError ? "failed" : "completed";
|
|
16298
16375
|
const errorCode = runError?.code ?? parseError?.code ?? reporterError?.code ?? (llmAbort ? "llm_api_error" : void 0);
|
|
16299
16376
|
const errorMessage = runError?.message ?? parseError?.message ?? reporterError?.message ?? (llmAbort ? llmErrorMessage ?? "LLM API error during turn" : void 0);
|
|
@@ -16395,6 +16472,25 @@ function summarizePayloadForLog(kind, payload) {
|
|
|
16395
16472
|
default: return payload;
|
|
16396
16473
|
}
|
|
16397
16474
|
}
|
|
16475
|
+
/**
|
|
16476
|
+
* Detect pi's bash-timeout error wrapper in a `tool_execution_end`
|
|
16477
|
+
* result. The bash tool surfaces a timeout as a structured tool result
|
|
16478
|
+
* `{ content: [{ type: 'text', text: '… Command timed out after N
|
|
16479
|
+
* seconds' }] }` (see `@earendil-works/pi-coding-agent`'s bash.js).
|
|
16480
|
+
* Substring-match against the stable wrapper string is the only
|
|
16481
|
+
* mechanism short of patching pi; the string is part of pi's external
|
|
16482
|
+
* tool-error API and changing it would break agents that read tool
|
|
16483
|
+
* errors.
|
|
16484
|
+
*/
|
|
16485
|
+
function isBashTimeoutResult(result) {
|
|
16486
|
+
if (result === null || result === void 0) return false;
|
|
16487
|
+
if (typeof result === "string") return result.includes("Command timed out after");
|
|
16488
|
+
if (typeof result !== "object") return false;
|
|
16489
|
+
const content = result.content;
|
|
16490
|
+
if (!Array.isArray(content)) return false;
|
|
16491
|
+
for (const part of content) if (typeof part === "object" && part !== null && typeof part.text === "string" && part.text.includes("Command timed out after")) return true;
|
|
16492
|
+
return false;
|
|
16493
|
+
}
|
|
16398
16494
|
var TRUNCATE_LIMIT = 4 * 1024;
|
|
16399
16495
|
function truncateForWire(value) {
|
|
16400
16496
|
if (value === null || value === void 0) return value;
|
|
@@ -16552,12 +16648,53 @@ async function maybeWriteAnchors(output, ctx) {
|
|
|
16552
16648
|
}
|
|
16553
16649
|
}
|
|
16554
16650
|
//#endregion
|
|
16651
|
+
//#region src/lib/logger.ts
|
|
16652
|
+
/**
|
|
16653
|
+
* Logger setup + teardown for the agent-daemon CLI commands.
|
|
16654
|
+
*
|
|
16655
|
+
* Why this helper exists: pino with `pino-pretty` transport spawns a
|
|
16656
|
+
* Node Worker thread for human-readable output. The Worker holds the
|
|
16657
|
+
* parent's TTYWrap handles ref'd; without an explicit `.end()` the
|
|
16658
|
+
* daemon never exits after the task completes — symptom: `once`
|
|
16659
|
+
* prints `[done] TaskOutput: …` then hangs until Ctrl-C
|
|
16660
|
+
* (see issue #1107). This module owns both creation and shutdown
|
|
16661
|
+
* so every CLI command gets the same cleanup contract without
|
|
16662
|
+
* duplicating the dance.
|
|
16663
|
+
*
|
|
16664
|
+
* Non-TTY mode (CI, file redirect) skips the transport entirely:
|
|
16665
|
+
* pino writes raw NDJSON directly to stderr, no worker thread,
|
|
16666
|
+
* nothing extra to clean up.
|
|
16667
|
+
*/
|
|
16668
|
+
function createRootLogger(options) {
|
|
16669
|
+
const prettyTransport = process.stderr.isTTY ? transport({
|
|
16670
|
+
target: "pino-pretty",
|
|
16671
|
+
options: { colorize: true }
|
|
16672
|
+
}) : null;
|
|
16673
|
+
const logger = pino(options, prettyTransport ?? void 0);
|
|
16674
|
+
const shutdown = async () => {
|
|
16675
|
+
if (!prettyTransport) return;
|
|
16676
|
+
logger.flush();
|
|
16677
|
+
try {
|
|
16678
|
+
prettyTransport.end();
|
|
16679
|
+
await once(prettyTransport, "close");
|
|
16680
|
+
} catch (err) {
|
|
16681
|
+
process.stderr.write(`[pino] transport teardown failed: ` + (err instanceof Error ? err.message : String(err)) + "\n");
|
|
16682
|
+
}
|
|
16683
|
+
};
|
|
16684
|
+
return {
|
|
16685
|
+
logger,
|
|
16686
|
+
shutdown
|
|
16687
|
+
};
|
|
16688
|
+
}
|
|
16689
|
+
//#endregion
|
|
16555
16690
|
//#region src/lib/options.ts
|
|
16556
16691
|
var DEFAULTS = {
|
|
16557
16692
|
leaseTtlSec: 300,
|
|
16558
16693
|
heartbeatIntervalMs: 6e4,
|
|
16559
16694
|
maxBatchSize: 50,
|
|
16560
|
-
flushIntervalMs: 200
|
|
16695
|
+
flushIntervalMs: 200,
|
|
16696
|
+
maxTurns: 0,
|
|
16697
|
+
maxBashTimeouts: 3
|
|
16561
16698
|
};
|
|
16562
16699
|
var MissingRequiredOptionError = class extends Error {
|
|
16563
16700
|
constructor(flag) {
|
|
@@ -16579,6 +16716,8 @@ function parseCommonOptions(args) {
|
|
|
16579
16716
|
heartbeatIntervalMs: parseNonNegativeInt(args["heartbeat-interval-ms"], "heartbeat-interval-ms", DEFAULTS.heartbeatIntervalMs),
|
|
16580
16717
|
maxBatchSize: parsePositiveInt(args["max-batch-size"], "max-batch-size", DEFAULTS.maxBatchSize),
|
|
16581
16718
|
flushIntervalMs: parseNonNegativeInt(args["flush-interval-ms"], "flush-interval-ms", DEFAULTS.flushIntervalMs),
|
|
16719
|
+
maxTurns: parseNonNegativeInt(args["max-turns"], "max-turns", DEFAULTS.maxTurns),
|
|
16720
|
+
maxBashTimeouts: parseNonNegativeInt(args["max-bash-timeouts"], "max-bash-timeouts", DEFAULTS.maxBashTimeouts),
|
|
16582
16721
|
debug: args.debug === true
|
|
16583
16722
|
};
|
|
16584
16723
|
}
|
|
@@ -16612,6 +16751,8 @@ function commonOptionDefs() {
|
|
|
16612
16751
|
"heartbeat-interval-ms": { type: "string" },
|
|
16613
16752
|
"max-batch-size": { type: "string" },
|
|
16614
16753
|
"flush-interval-ms": { type: "string" },
|
|
16754
|
+
"max-turns": { type: "string" },
|
|
16755
|
+
"max-bash-timeouts": { type: "string" },
|
|
16615
16756
|
debug: { type: "boolean" }
|
|
16616
16757
|
};
|
|
16617
16758
|
}
|
|
@@ -16778,14 +16919,11 @@ async function runPolling(opts) {
|
|
|
16778
16919
|
"moltnet.llm.model": common.model
|
|
16779
16920
|
}
|
|
16780
16921
|
});
|
|
16781
|
-
const
|
|
16922
|
+
const { logger, shutdown: shutdownLogger } = createRootLogger({
|
|
16782
16923
|
name: `agent-daemon.${opts.modeLabel}`,
|
|
16783
|
-
level: cfg.logLevel || (common.debug ? "debug" : "info")
|
|
16784
|
-
|
|
16785
|
-
|
|
16786
|
-
options: { colorize: true }
|
|
16787
|
-
} } : {}
|
|
16788
|
-
}).child({
|
|
16924
|
+
level: cfg.logLevel || (common.debug ? "debug" : "info")
|
|
16925
|
+
});
|
|
16926
|
+
const rootLogger = logger.child({
|
|
16789
16927
|
mode: opts.modeLabel,
|
|
16790
16928
|
agent: common.agent,
|
|
16791
16929
|
teamId,
|
|
@@ -16818,7 +16956,9 @@ async function runPolling(opts) {
|
|
|
16818
16956
|
provider: common.provider,
|
|
16819
16957
|
model: common.model,
|
|
16820
16958
|
sandboxConfig: sandbox.config,
|
|
16821
|
-
makeOnTurnEvent: makeTurnEventHandlerFactory(rootLogger)
|
|
16959
|
+
makeOnTurnEvent: makeTurnEventHandlerFactory(rootLogger),
|
|
16960
|
+
maxTurns: common.maxTurns,
|
|
16961
|
+
maxBashTimeouts: common.maxBashTimeouts
|
|
16822
16962
|
});
|
|
16823
16963
|
runtime = new AgentRuntime({
|
|
16824
16964
|
logger: rootLogger,
|
|
@@ -16897,6 +17037,7 @@ async function runPolling(opts) {
|
|
|
16897
17037
|
return drained.some((o) => o.status !== "completed") ? 1 : 0;
|
|
16898
17038
|
} finally {
|
|
16899
17039
|
await otelShutdown();
|
|
17040
|
+
await shutdownLogger();
|
|
16900
17041
|
}
|
|
16901
17042
|
}
|
|
16902
17043
|
function parseCsv(raw) {
|
|
@@ -16968,14 +17109,11 @@ async function runOnce(argv) {
|
|
|
16968
17109
|
"moltnet.llm.model": opts.model
|
|
16969
17110
|
}
|
|
16970
17111
|
});
|
|
16971
|
-
const
|
|
17112
|
+
const { logger, shutdown: shutdownLogger } = createRootLogger({
|
|
16972
17113
|
name: "agent-daemon.once",
|
|
16973
|
-
level: cfg.logLevel || (opts.debug ? "debug" : "info")
|
|
16974
|
-
|
|
16975
|
-
|
|
16976
|
-
options: { colorize: true }
|
|
16977
|
-
} } : {}
|
|
16978
|
-
}).child({
|
|
17114
|
+
level: cfg.logLevel || (opts.debug ? "debug" : "info")
|
|
17115
|
+
});
|
|
17116
|
+
const rootLogger = logger.child({
|
|
16979
17117
|
mode: "once",
|
|
16980
17118
|
agent: opts.agent,
|
|
16981
17119
|
provider: opts.provider,
|
|
@@ -17012,7 +17150,9 @@ async function runOnce(argv) {
|
|
|
17012
17150
|
provider: opts.provider,
|
|
17013
17151
|
model: opts.model,
|
|
17014
17152
|
sandboxConfig: sandbox.config,
|
|
17015
|
-
onTurnEvent: makeTurnEventHandler(rootLogger, { taskId })
|
|
17153
|
+
onTurnEvent: makeTurnEventHandler(rootLogger, { taskId }),
|
|
17154
|
+
maxTurns: opts.maxTurns,
|
|
17155
|
+
maxBashTimeouts: opts.maxBashTimeouts
|
|
17016
17156
|
});
|
|
17017
17157
|
const writeCorrelationAnchors = makePrBodyAnchorWriter({
|
|
17018
17158
|
gh: createGhCliClient(),
|
|
@@ -17049,6 +17189,7 @@ async function runOnce(argv) {
|
|
|
17049
17189
|
return output.status === "completed" ? 0 : 1;
|
|
17050
17190
|
} finally {
|
|
17051
17191
|
await otelShutdown();
|
|
17192
|
+
await shutdownLogger();
|
|
17052
17193
|
}
|
|
17053
17194
|
}
|
|
17054
17195
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@themoltnet/agent-daemon",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
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.",
|
|
@@ -35,17 +35,17 @@
|
|
|
35
35
|
"pino-pretty": "^13.1.3",
|
|
36
36
|
"@themoltnet/agent-runtime": "0.15.0",
|
|
37
37
|
"@themoltnet/sdk": "0.102.0",
|
|
38
|
-
"@themoltnet/pi-extension": "0.
|
|
38
|
+
"@themoltnet/pi-extension": "0.16.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"tsx": "^4.7.0",
|
|
42
42
|
"typescript": "^5.3.3",
|
|
43
43
|
"vite": "^8.0.0",
|
|
44
44
|
"vitest": "^3.0.0",
|
|
45
|
-
"@moltnet/crypto-service": "0.1.0",
|
|
46
|
-
"@moltnet/database": "0.1.0",
|
|
47
45
|
"@moltnet/bootstrap": "0.1.0",
|
|
48
|
-
"@moltnet/
|
|
46
|
+
"@moltnet/crypto-service": "0.1.0",
|
|
47
|
+
"@moltnet/tasks": "0.1.0",
|
|
48
|
+
"@moltnet/database": "0.1.0"
|
|
49
49
|
},
|
|
50
50
|
"nx": {
|
|
51
51
|
"tags": [
|