@tea-agent/loop-agent 0.10.0 → 0.11.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/AGENTS.md +10 -2
- package/CHANGELOG.md +67 -25
- package/README.md +82 -11
- package/dist/application/dag/args.js +1 -12
- package/dist/application/dag/generate-task-dag.js +23 -2
- package/dist/application/dag/run-dag.js +1 -27
- package/dist/application/dag/validate-dag.js +2 -2
- package/dist/application/loop/run-action.js +0 -4
- package/dist/cli/command-definitions.js +44 -16
- package/dist/cli/program.js +40 -23
- package/dist/cli/update/notifier.js +117 -0
- package/dist/cli/update/npm-client.js +151 -0
- package/dist/cli/update/policy.js +58 -0
- package/dist/cli/update/state.js +68 -0
- package/dist/cli.js +33 -0
- package/dist/commands/cursor-prompt.js +42 -82
- package/dist/commands/dag-approve.js +36 -0
- package/dist/commands/delegate.js +75 -77
- package/dist/commands/doctor.js +0 -18
- package/dist/commands/init.js +476 -91
- package/dist/commands/instructions.js +7 -10
- package/dist/commands/loop.js +4 -20
- package/dist/commands/plan.js +50 -0
- package/dist/executors/config-core.js +0 -51
- package/dist/executors/dag-pi-executor.js +1 -1
- package/dist/executors/dag.js +0 -1
- package/dist/executors/index.js +0 -2
- package/dist/executors/model-routing.js +9 -9
- package/dist/executors/shell-executor.js +1 -1
- package/dist/governance/checks.js +6 -3
- package/dist/governance/exec-plans.js +545 -0
- package/dist/governance/manifest-types.js +24 -2
- package/dist/infrastructure/harness/loop-action-store.js +0 -3
- package/dist/records/harvest.js +2 -23
- package/dist/records/one-shot-runs.js +1 -1
- package/dist/shared/artifacts-core.js +24 -5
- package/dist/shared/output-truncation.js +37 -0
- package/dist/shared/package-metadata.js +353 -0
- package/dist/{executors/cursor-executor.js → sidecars/cursor-prompt/executor.js} +2 -42
- package/dist/sidecars/cursor-prompt/index.js +3 -0
- package/dist/sidecars/cursor-prompt/stream.js +121 -0
- package/dist/task/config-types.js +28 -12
- package/dist/task/delegate.js +9 -21
- package/dist/task/runtime.js +1 -2
- package/dist/worker/cli.js +29 -2
- package/dist/worker/delivery/final-verification.js +47 -11
- package/dist/worker/delivery/package.js +63 -10
- package/dist/worker/feature/run.js +60 -8
- package/dist/worker/loop-agent/loop-agent-client.js +329 -126
- package/dist/worker/observability/read-model.js +27 -1
- package/dist/worker/observe/static/app.js +326 -45
- package/dist/worker/observe/static/index.html +1 -1
- package/dist/worker/observe/static/styles.css +5 -4
- package/dist/worker/preflight.js +49 -1
- package/dist/worker/run-task/run-task.js +22 -12
- package/dist/worker/runner/run-ready.js +76 -12
- package/dist/worker/task-spec/schema.js +0 -1
- package/dist/workflows/dag/convergence/controller.js +1 -1
- package/dist/workflows/dag/executor-registry.js +0 -2
- package/dist/workflows/dag/init-hybrid.js +402 -25
- package/dist/workflows/dag/node-execution.js +61 -7
- package/dist/workflows/dag/runner.js +45 -17
- package/dist/workflows/dag/scheduler.js +7 -2
- package/dist/workflows/dag/sdd-embedded.js +128 -0
- package/dist/workflows/dag/skill-instructions.js +5 -4
- package/dist/workflows/dag/skill-snapshot.js +527 -0
- package/dist/workflows/dag/types.js +42 -9
- package/dist/workflows/dag/validate.js +5 -8
- package/dist/workflows/loop/actions/dag-action.js +0 -2
- package/dist/workflows/loop/actions/shared.js +1 -1
- package/dist/workflows/loop/actions.js +14 -31
- package/dist/workflows/loop/benchmark.js +1 -1
- package/dist/workflows/loop/index.js +1 -1
- package/dist/workflows/loop/policy/auto-policy.js +22 -14
- package/dist/workflows/loop/policy/path-patterns.js +13 -0
- package/docs/README.md +36 -33
- package/docs/agent-dag-recovery-playbook.md +1 -1
- package/docs/agent-dag-runner.md +2 -2
- package/docs/architecture/README.md +26 -0
- package/docs/architecture/dag-execution.md +134 -0
- package/docs/architecture/evolution.md +52 -0
- package/docs/architecture/facts-and-state.md +58 -0
- package/docs/architecture/runtime-boundaries.md +45 -17
- package/docs/architecture/system-overview.md +93 -0
- package/docs/architecture/worker-and-feature.md +81 -0
- package/docs/cursor-prompt-sidecar.md +36 -0
- package/docs/decisions/README.md +13 -1
- package/docs/design/README.md +42 -21
- package/docs/development-principles.md +2 -2
- package/docs/exec-plans/active/README.md +2 -2
- package/docs/exec-plans/completed/README.md +12 -0
- package/docs/feature-workflow.md +50 -4
- package/docs/harness-methodology-debugging.md +1 -1
- package/docs/harness-methodology-tdd.md +3 -3
- package/docs/init-surface.manifest.json +60 -25
- package/docs/loop-agent-harness.md +28 -4
- package/docs/progress/README.md +32 -1
- package/docs/reports/README.md +84 -18
- package/docs/skills/README.md +2 -1
- package/docs/skills/vetted-skill-registry.md +2 -1
- package/docs/templates/agent-dag-report.schema.json +6 -6
- package/docs/templates/agent-dag.base.json +0 -5
- package/docs/templates/agent-dag.final-verification.json +0 -5
- package/docs/templates/agent-dag.schema.json +1 -2
- package/docs/templates/agent-dag.supervised-implementation.json +1 -6
- package/docs/templates/frontend-design-contract.md +33 -0
- package/docs/templates/frontend-task-constraints.md +25 -0
- package/docs/templates/frontend-task-requirement.md +61 -0
- package/docs/templates/harness.schema.json +10 -12
- package/docs/templates/hybrid-dag.json +1 -6
- package/docs/templates/interactive-ui-round2-experiment.md +1 -1
- package/docs/templates/product-line/task.yaml +0 -1
- package/docs/templates/project-start-checklist.md +2 -2
- package/docs/templates/worker-dogfood-evidence.md +28 -0
- package/docs/templates/worker-dogfood-setup.md +20 -0
- package/docs/verification-matrix.md +10 -0
- package/examples/decision-gate-agent-dag.json +87 -33
- package/examples/example-dag.json +0 -5
- package/examples/hybrid-loop-agent-dag.json +0 -5
- package/harness.json +7 -15
- package/package.json +22 -46
- package/scripts/check-product-line-docs.sh +10 -7
- package/skills/agent-worker/SKILL.md +37 -0
- package/skills/agent-worker/references/agent-worker-operator.md +43 -0
- package/skills/frontend-design-review/SKILL.md +59 -0
- package/skills/frontend-design-review/references/review-checklist.md +37 -0
- package/skills/frontend-implementation/SKILL.md +51 -0
- package/skills/frontend-implementation/references/code-standards.md +34 -0
- package/skills/frontend-implementation/references/design-spec.md +46 -0
- package/skills/frontend-implementation/references/node-contracts.md +32 -0
- package/skills/frontend-review/SKILL.md +53 -0
- package/skills/frontend-review/references/review-findings.md +42 -0
- package/skills/frontend-verification/SKILL.md +40 -0
- package/skills/frontend-verification/references/verification-checklist.md +56 -0
- package/skills/grill-me/SKILL.md +10 -0
- package/skills/grill-with-docs/SKILL.md +88 -0
- package/skills/grill-with-docs/adr-format.md +47 -0
- package/skills/grill-with-docs/context-format.md +60 -0
- package/skills/loop-agent/SKILL.md +11 -9
- package/skills/loop-agent/references/command-reference.md +13 -15
- package/skills/loop-agent/references/docs-converge.md +126 -0
- package/skills/loop-agent/references/harness-policy.md +7 -7
- package/skills/loop-agent/references/hybrid-dag.md +15 -18
- package/skills/loop-agent/references/long-running-loop.md +4 -6
- package/skills/loop-agent/references/multi-worktree.md +6 -6
- package/skills/loop-agent/references/orchestrator-and-interventions.md +3 -3
- package/skills/loop-agent/references/pi-subagent-assisted-mode.md +14 -11
- package/skills/loop-agent/references/task-workflow.md +1 -1
- package/skills/using-git-worktrees/SKILL.md +215 -0
- package/dist/commands/cursor-worker.js +0 -43
- package/dist/cursor-worker-entry.js +0 -8
- package/dist/executors/cursor-artifacts.js +0 -33
- package/dist/executors/cursor-execution-log.js +0 -81
- package/dist/executors/cursor-executor-artifacts.js +0 -134
- package/dist/executors/cursor-run.js +0 -115
- package/dist/executors/cursor-tool.js +0 -94
- package/dist/executors/cursor-worker-client.js +0 -223
- package/dist/executors/cursor-worker-protocol.js +0 -18
- package/dist/executors/cursor-worker-server.js +0 -54
- package/dist/executors/cursor-worker.js +0 -3
- package/dist/executors/cursor.js +0 -6
- package/dist/executors/dag-cursor-executor.js +0 -87
- package/dist/workflows/loop/actions/cursor-fix.js +0 -191
- package/dist/workflows/loop/policy/cursor-fix-policy.js +0 -31
- package/docs/cursor-executor-usage.md +0 -25
- package/docs/dynamic-workflow-dag-engine-roadmap.md +0 -1749
|
@@ -10,6 +10,7 @@ import { runInstructions } from "../commands/instructions.js";
|
|
|
10
10
|
import { runNewTask } from "../commands/new-task.js";
|
|
11
11
|
import { runImportPrd } from "../commands/import-prd.js";
|
|
12
12
|
import { runPlanList } from "../commands/plan-list.js";
|
|
13
|
+
import { runPlanCheck, runPlanComplete, runPlanCreate } from "../commands/plan.js";
|
|
13
14
|
import { runPromoteRun } from "../commands/promote-run.js";
|
|
14
15
|
import { runSpine } from "../commands/spine.js";
|
|
15
16
|
import { runStats } from "../commands/stats.js";
|
|
@@ -36,7 +37,6 @@ import { runDagDoctor, runDagStatus } from "../workflows/dag/lifecycle.js";
|
|
|
36
37
|
import { runDagRunTask } from "../commands/dag-run-task.js";
|
|
37
38
|
import { runDagCloseoutDraft, runDagReport } from "../commands/dag-report.js";
|
|
38
39
|
import { runDagFinalVerification } from "../commands/dag-final-verification.js";
|
|
39
|
-
import { runCursorWorker } from "../commands/cursor-worker.js";
|
|
40
40
|
import { runKnowledge } from "../commands/knowledge.js";
|
|
41
41
|
import { runLoopBenchmark } from "../commands/loop-benchmark.js";
|
|
42
42
|
import { runPiReuseBenchmark } from "../commands/pi-reuse-benchmark.js";
|
|
@@ -51,6 +51,23 @@ export function formatSubcommandUsage(name, subcommands) {
|
|
|
51
51
|
export function formatSubcommandUsageError(name, subcommands) {
|
|
52
52
|
return `usage: ${formatSubcommandUsage(name, subcommands)}`;
|
|
53
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Parse a `--summary "..."` flag from a token list. Supports both
|
|
56
|
+
* `--summary <value>` and `--summary=<value>` forms. Returns undefined when
|
|
57
|
+
* the flag is absent so callers can emit their own usage error.
|
|
58
|
+
*/
|
|
59
|
+
export function parseSummaryFlag(tokens) {
|
|
60
|
+
for (let i = 0; i < tokens.length; i += 1) {
|
|
61
|
+
const token = tokens[i];
|
|
62
|
+
if (token === "--summary") {
|
|
63
|
+
return tokens[i + 1];
|
|
64
|
+
}
|
|
65
|
+
if (token?.startsWith("--summary=")) {
|
|
66
|
+
return token.slice("--summary=".length);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
54
71
|
const DOCS_SUBCOMMANDS = ["audit", "archive"];
|
|
55
72
|
const INIT_SUBCOMMANDS = [
|
|
56
73
|
"instructions",
|
|
@@ -60,7 +77,7 @@ const INIT_SUBCOMMANDS = [
|
|
|
60
77
|
];
|
|
61
78
|
const EXAMPLES_SUBCOMMANDS = ["list", "show", "copy"];
|
|
62
79
|
const CLOSEOUT_SUBCOMMANDS = ["task"];
|
|
63
|
-
const PLAN_SUBCOMMANDS = ["list"];
|
|
80
|
+
const PLAN_SUBCOMMANDS = ["list", "create", "complete", "check"];
|
|
64
81
|
const SPINE_SUBCOMMANDS = ["audit"];
|
|
65
82
|
const HANDOFF_SUBCOMMANDS = ["check", "coverage"];
|
|
66
83
|
const HANDOFF_USAGE = "handoff <check|coverage> [taskId]";
|
|
@@ -251,14 +268,35 @@ export const COMMAND_DEFINITIONS = [
|
|
|
251
268
|
name: "plan",
|
|
252
269
|
adapter: "required",
|
|
253
270
|
tier: "operator",
|
|
254
|
-
intent: "
|
|
255
|
-
usage:
|
|
271
|
+
intent: "Manage repo execution plans: list, create, complete, and index check.",
|
|
272
|
+
usage: "plan <list|create|complete|check> [plan-id] [title|--summary <summary>]",
|
|
256
273
|
subcommands: [...PLAN_SUBCOMMANDS],
|
|
257
|
-
handler: async ({ repoRoot, subcommand }) => {
|
|
258
|
-
if (subcommand === "list") {
|
|
274
|
+
handler: async ({ repoRoot, subcommand, rest }) => {
|
|
275
|
+
if (subcommand === "list" || subcommand === undefined) {
|
|
259
276
|
await runPlanList(repoRoot);
|
|
260
277
|
return;
|
|
261
278
|
}
|
|
279
|
+
if (subcommand === "create") {
|
|
280
|
+
const [planId, ...titleParts] = rest;
|
|
281
|
+
if (!planId)
|
|
282
|
+
throw new Error("usage: plan create <plan-id> \"<title>\"");
|
|
283
|
+
await runPlanCreate(repoRoot, planId, titleParts.join(" "));
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (subcommand === "complete") {
|
|
287
|
+
const [planId, ...summaryParts] = rest;
|
|
288
|
+
if (!planId)
|
|
289
|
+
throw new Error("usage: plan complete <plan-id> --summary \"<summary>\"");
|
|
290
|
+
const summary = parseSummaryFlag(summaryParts);
|
|
291
|
+
if (!summary)
|
|
292
|
+
throw new Error("usage: plan complete <plan-id> --summary \"<summary>\"");
|
|
293
|
+
await runPlanComplete(repoRoot, planId, { summary });
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (subcommand === "check") {
|
|
297
|
+
await runPlanCheck(repoRoot);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
262
300
|
throw new Error(formatSubcommandUsageError("plan", PLAN_SUBCOMMANDS));
|
|
263
301
|
},
|
|
264
302
|
},
|
|
@@ -393,16 +431,6 @@ export const COMMAND_DEFINITIONS = [
|
|
|
393
431
|
await runHarvest(repoRoot, harvestArgs);
|
|
394
432
|
},
|
|
395
433
|
},
|
|
396
|
-
{
|
|
397
|
-
name: "cursor-worker",
|
|
398
|
-
adapter: "required",
|
|
399
|
-
tier: "escape-hatch",
|
|
400
|
-
intent: "Inspect or control the optional Cursor SDK worker process.",
|
|
401
|
-
usage: "cursor-worker <status|stop|ping>",
|
|
402
|
-
handler: async ({ repoRoot, subcommand, rest }) => {
|
|
403
|
-
await runCursorWorker(repoRoot, [subcommand, ...rest].filter((arg) => Boolean(arg)));
|
|
404
|
-
},
|
|
405
|
-
},
|
|
406
434
|
{
|
|
407
435
|
name: "knowledge",
|
|
408
436
|
adapter: "required",
|
package/dist/cli/program.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
1
|
import path from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
3
|
import { Command } from "commander";
|
|
4
|
+
import { readPackageVersion } from "../shared/package-metadata.js";
|
|
4
5
|
import { resolveAdapter } from "../adapters/index.js";
|
|
5
6
|
import { runCloseout } from "../commands/closeout.js";
|
|
6
7
|
import { runCoverageAudit } from "../commands/coverage-audit.js";
|
|
7
|
-
import { runCursorWorker } from "../commands/cursor-worker.js";
|
|
8
8
|
import { parseCursorPromptArgs, printCursorPromptUsage, runCursorPrompt, } from "../commands/cursor-prompt.js";
|
|
9
9
|
import { runDagApprove } from "../commands/dag-approve.js";
|
|
10
10
|
import { runDagFinalVerification } from "../commands/dag-final-verification.js";
|
|
@@ -38,6 +38,7 @@ import { runImportPrd } from "../commands/import-prd.js";
|
|
|
38
38
|
import { runPiReuseBenchmark } from "../commands/pi-reuse-benchmark.js";
|
|
39
39
|
import { parsePiPromptArgs, printPiPromptUsage, runPiPrompt, } from "../commands/pi-prompt.js";
|
|
40
40
|
import { runPlanList } from "../commands/plan-list.js";
|
|
41
|
+
import { runPlanCheck, runPlanComplete, runPlanCreate } from "../commands/plan.js";
|
|
41
42
|
import { runPromoteRun } from "../commands/promote-run.js";
|
|
42
43
|
import { runReferenceIndex } from "../commands/reference-index.js";
|
|
43
44
|
import { runRunDag } from "../commands/run-dag.js";
|
|
@@ -53,18 +54,6 @@ import { handleStandaloneOneShotCommandError } from "../shared/one-shot-prompt-a
|
|
|
53
54
|
import { runDagDecisionInspect, runDagDecisionValidate, } from "../workflows/dag/decision-envelope.js";
|
|
54
55
|
import { runDagDoctor, runDagStatus } from "../workflows/dag/lifecycle.js";
|
|
55
56
|
import { CODE_AGENT_COMMAND_MANIFEST } from "./catalog.js";
|
|
56
|
-
function readPackageVersion() {
|
|
57
|
-
try {
|
|
58
|
-
const raw = readFileSync(new URL("../../package.json", import.meta.url), "utf-8");
|
|
59
|
-
const parsed = JSON.parse(raw);
|
|
60
|
-
if (typeof parsed.version === "string")
|
|
61
|
-
return parsed.version;
|
|
62
|
-
}
|
|
63
|
-
catch {
|
|
64
|
-
// Fall back only if the package metadata is unexpectedly unavailable.
|
|
65
|
-
}
|
|
66
|
-
return "0.0.0";
|
|
67
|
-
}
|
|
68
57
|
function resolveRepoRootOnly(command, defaultRepoRoot, behavior) {
|
|
69
58
|
const options = collectGlobalOptions(command);
|
|
70
59
|
const repoRoot = path.resolve(options.repoRoot ?? defaultRepoRoot);
|
|
@@ -167,11 +156,32 @@ async function runCommanderAction(ctx, command, subcommand, rest) {
|
|
|
167
156
|
await runStats(ctx.repoRoot);
|
|
168
157
|
return;
|
|
169
158
|
case "plan":
|
|
170
|
-
if (subcommand === "list") {
|
|
159
|
+
if (subcommand === "list" || subcommand === undefined) {
|
|
171
160
|
await runPlanList(ctx.repoRoot);
|
|
172
161
|
return;
|
|
173
162
|
}
|
|
174
|
-
|
|
163
|
+
if (subcommand === "create") {
|
|
164
|
+
const [planId, ...titleParts] = rest;
|
|
165
|
+
if (!planId)
|
|
166
|
+
throw new Error("usage: plan create <plan-id> \"<title>\"");
|
|
167
|
+
await runPlanCreate(ctx.repoRoot, planId, titleParts.join(" "));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (subcommand === "complete") {
|
|
171
|
+
const [planId, ...summaryParts] = rest;
|
|
172
|
+
if (!planId)
|
|
173
|
+
throw new Error("usage: plan complete <plan-id> --summary \"<summary>\"");
|
|
174
|
+
const summary = parsePlanSummaryFlag(summaryParts);
|
|
175
|
+
if (!summary)
|
|
176
|
+
throw new Error("usage: plan complete <plan-id> --summary \"<summary>\"");
|
|
177
|
+
await runPlanComplete(ctx.repoRoot, planId, { summary });
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (subcommand === "check") {
|
|
181
|
+
await runPlanCheck(ctx.repoRoot);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
throw new Error("usage: plan <list|create|complete|check> ...");
|
|
175
185
|
case "spine":
|
|
176
186
|
await runSpine(ctx.repoRoot, compactArgs([subcommand, ...rest]));
|
|
177
187
|
return;
|
|
@@ -231,9 +241,6 @@ async function runCommanderAction(ctx, command, subcommand, rest) {
|
|
|
231
241
|
case "harvest":
|
|
232
242
|
await runHarvest(ctx.repoRoot, compactArgs([subcommand, ...rest]));
|
|
233
243
|
return;
|
|
234
|
-
case "cursor-worker":
|
|
235
|
-
await runCursorWorker(ctx.repoRoot, compactArgs([subcommand, ...rest]));
|
|
236
|
-
return;
|
|
237
244
|
case "knowledge":
|
|
238
245
|
await runKnowledge(ctx.repoRoot, compactArgs([subcommand, ...rest]));
|
|
239
246
|
return;
|
|
@@ -329,6 +336,18 @@ async function runDagAction(repoRoot, subcommand, rest) {
|
|
|
329
336
|
function compactArgs(args) {
|
|
330
337
|
return args.filter((arg) => Boolean(arg));
|
|
331
338
|
}
|
|
339
|
+
function parsePlanSummaryFlag(tokens) {
|
|
340
|
+
for (let i = 0; i < tokens.length; i += 1) {
|
|
341
|
+
const token = tokens[i];
|
|
342
|
+
if (token === "--summary") {
|
|
343
|
+
return tokens[i + 1];
|
|
344
|
+
}
|
|
345
|
+
if (token?.startsWith("--summary=")) {
|
|
346
|
+
return token.slice("--summary=".length);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return undefined;
|
|
350
|
+
}
|
|
332
351
|
function createRawArgsCommand(name, description, defaultRepoRoot) {
|
|
333
352
|
return new Command(name)
|
|
334
353
|
.description(description)
|
|
@@ -375,7 +394,6 @@ function configureRunDag(command) {
|
|
|
375
394
|
.option("--dry-run", "validate and plan without executing")
|
|
376
395
|
.option("--max-concurrent <n>", "maximum concurrent nodes")
|
|
377
396
|
.option("--run-id <id>", "run id")
|
|
378
|
-
.option("--no-cursor", "disable Cursor execution")
|
|
379
397
|
.option("--canvas-path <path>", "absolute canvas output path")
|
|
380
398
|
.option("--canvas <name>", "canvas name")
|
|
381
399
|
.option("--canvases-dir <dir>", "canvas output directory")
|
|
@@ -387,7 +405,6 @@ function configureDagRunTask(command) {
|
|
|
387
405
|
.option("-o, --output <path>", "output DAG path")
|
|
388
406
|
.option("--profile <profile>", "auto|minimal|standard|reviewed|supervised")
|
|
389
407
|
.option("--strict-models", "fail on model routing warnings")
|
|
390
|
-
.option("--no-cursor", "disable Cursor execution")
|
|
391
408
|
.option("--execute", "execute after creating the DAG")
|
|
392
409
|
.option("--init-only", "initialize run artifacts without executing")
|
|
393
410
|
.option("--dry-run", "validate and plan without executing")
|
|
@@ -507,7 +524,7 @@ export function buildLoopAgentProgram(options) {
|
|
|
507
524
|
program
|
|
508
525
|
.name("loop-agent")
|
|
509
526
|
.description("Loop Agent task and DAG automation CLI")
|
|
510
|
-
.version(readPackageVersion(), "-V, --version", "display version")
|
|
527
|
+
.version(readPackageVersion(path.dirname(path.dirname(path.dirname(fileURLToPath(import.meta.url))))) ?? "0.0.0", "-V, --version", "display version")
|
|
511
528
|
.showHelpAfterError()
|
|
512
529
|
.option("--repo-root <path>", "repository root")
|
|
513
530
|
.option("--adapter <name>", "harness adapter name")
|
|
@@ -544,7 +561,7 @@ export function buildLoopAgentProgram(options) {
|
|
|
544
561
|
.allowExcessArguments(true)
|
|
545
562
|
.argument("[args...]", "init arguments")
|
|
546
563
|
.option("--project-name <name>", "target project name")
|
|
547
|
-
.option("--governance-root <path>", "governance root"
|
|
564
|
+
.option("--governance-root <path>", "governance root")
|
|
548
565
|
.option("--profile <profile>", "full|minimal", "full")
|
|
549
566
|
.option("--merge", "merge existing files")
|
|
550
567
|
.option("--no-merge", "skip existing files")
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { createInterface } from "node:readline/promises";
|
|
2
|
+
import { evaluateLatestVersion, shouldCheckForUpdates } from "./policy.js";
|
|
3
|
+
import { acquireUpdateLock, readUpdateState, updateLockPath, updateStatePath, writeUpdateState, } from "./state.js";
|
|
4
|
+
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
5
|
+
const FAILURE_COOLDOWN_MS = 10 * 60 * 1000;
|
|
6
|
+
export async function runSelfUpdateNotifier(options) {
|
|
7
|
+
if (!shouldCheckForUpdates(options))
|
|
8
|
+
return;
|
|
9
|
+
const now = options.now ?? (() => new Date());
|
|
10
|
+
const writeStderr = options.writeStderr ?? ((chunk) => process.stderr.write(chunk));
|
|
11
|
+
void options.writeStdout;
|
|
12
|
+
try {
|
|
13
|
+
const statePath = updateStatePath(options.homeDir);
|
|
14
|
+
let state = await readUpdateState(statePath);
|
|
15
|
+
if (state.retryAfter && Date.parse(state.retryAfter) > now().getTime())
|
|
16
|
+
return;
|
|
17
|
+
let latestVersion = cachedLatest(state, now());
|
|
18
|
+
if (!latestVersion) {
|
|
19
|
+
latestVersion = await options.client.latestVersion();
|
|
20
|
+
state = {
|
|
21
|
+
...state,
|
|
22
|
+
lastCheckedAt: now().toISOString(),
|
|
23
|
+
latestVersion,
|
|
24
|
+
};
|
|
25
|
+
await safeWriteState(statePath, state);
|
|
26
|
+
}
|
|
27
|
+
const decision = evaluateLatestVersion({
|
|
28
|
+
currentVersion: options.currentVersion,
|
|
29
|
+
latestVersion,
|
|
30
|
+
dismissedVersion: state.dismissedVersion,
|
|
31
|
+
now: now(),
|
|
32
|
+
retryAfter: state.retryAfter,
|
|
33
|
+
});
|
|
34
|
+
if (!decision.shouldPrompt)
|
|
35
|
+
return;
|
|
36
|
+
const release = await acquireUpdateLock(updateLockPath(options.homeDir), { now });
|
|
37
|
+
if (!release)
|
|
38
|
+
return;
|
|
39
|
+
try {
|
|
40
|
+
state = await readUpdateState(statePath);
|
|
41
|
+
if (state.dismissedVersion === decision.targetVersion)
|
|
42
|
+
return;
|
|
43
|
+
const answer = await ask(options, `loop-agent 有新版本:${options.currentVersion} → ${decision.targetVersion}\n现在更新到 ${decision.targetVersion}?[y/N] `);
|
|
44
|
+
if (!isYes(answer)) {
|
|
45
|
+
await safeWriteState(statePath, {
|
|
46
|
+
...state,
|
|
47
|
+
latestVersion: decision.targetVersion,
|
|
48
|
+
dismissedVersion: decision.targetVersion,
|
|
49
|
+
});
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const proof = await options.client.canAutoInstall();
|
|
53
|
+
if (!proof.ok) {
|
|
54
|
+
writeStderr(`当前 loop-agent 不是由此 npm 的正式全局安装管理,无法自动升级。\n请运行:npm install -g @tea-agent/loop-agent@${decision.targetVersion}\n`);
|
|
55
|
+
await safeWriteState(statePath, cooldownState(state, now()));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const install = await options.client.installExact(decision.targetVersion);
|
|
59
|
+
if (!install.ok) {
|
|
60
|
+
writeStderr(`loop-agent 更新失败:${install.reason}\n`);
|
|
61
|
+
await safeWriteState(statePath, cooldownState(state, now()));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const verified = await options.client.verifyInstalled(decision.targetVersion);
|
|
65
|
+
if (!verified.ok) {
|
|
66
|
+
writeStderr(`loop-agent 已尝试安装,但验证失败:${verified.reason}\n`);
|
|
67
|
+
await safeWriteState(statePath, cooldownState(state, now()));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
writeStderr(`loop-agent 已更新到 ${decision.targetVersion};新版本将在下一次调用时生效。\n`);
|
|
71
|
+
await safeWriteState(statePath, {
|
|
72
|
+
schemaVersion: 1,
|
|
73
|
+
lastCheckedAt: now().toISOString(),
|
|
74
|
+
latestVersion: decision.targetVersion,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
await release().catch(() => undefined);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
writeStderr(`loop-agent 更新检查已跳过:${error instanceof Error ? error.message : String(error)}\n`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function cachedLatest(state, now) {
|
|
86
|
+
if (!state.lastCheckedAt || !state.latestVersion)
|
|
87
|
+
return undefined;
|
|
88
|
+
const checkedAt = Date.parse(state.lastCheckedAt);
|
|
89
|
+
if (!Number.isFinite(checkedAt))
|
|
90
|
+
return undefined;
|
|
91
|
+
return now.getTime() - checkedAt <= CHECK_INTERVAL_MS ? state.latestVersion : undefined;
|
|
92
|
+
}
|
|
93
|
+
async function ask(options, prompt) {
|
|
94
|
+
if (options.readLine) {
|
|
95
|
+
options.writeStderr?.(prompt);
|
|
96
|
+
return options.readLine(prompt);
|
|
97
|
+
}
|
|
98
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
99
|
+
try {
|
|
100
|
+
return await rl.question(prompt);
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
rl.close();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function isYes(answer) {
|
|
107
|
+
return /^(y|yes)$/i.test(answer.trim());
|
|
108
|
+
}
|
|
109
|
+
function cooldownState(state, now) {
|
|
110
|
+
return {
|
|
111
|
+
...state,
|
|
112
|
+
retryAfter: new Date(now.getTime() + FAILURE_COOLDOWN_MS).toISOString(),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
async function safeWriteState(path, state) {
|
|
116
|
+
await writeUpdateState(path, state).catch(() => undefined);
|
|
117
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { lstat, readFile, realpath } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import semver from "semver";
|
|
5
|
+
const PACKAGE_NAME = "@tea-agent/loop-agent";
|
|
6
|
+
export function createNpmUpdateClient(options) {
|
|
7
|
+
const npmCommand = options.npmCommand ?? "npm";
|
|
8
|
+
const loopAgentCommand = options.loopAgentCommand ?? "loop-agent";
|
|
9
|
+
const runner = options.runner ?? runCommand;
|
|
10
|
+
const timeoutMs = options.timeoutMs ?? 1500;
|
|
11
|
+
const env = options.env;
|
|
12
|
+
return {
|
|
13
|
+
async latestVersion() {
|
|
14
|
+
const result = await runner(npmCommand, ["view", PACKAGE_NAME, "dist-tags.latest", "--json"], { timeoutMs, env });
|
|
15
|
+
if (result.exitCode !== 0 || result.timedOut)
|
|
16
|
+
return undefined;
|
|
17
|
+
return parseNpmViewLatest(result.stdout);
|
|
18
|
+
},
|
|
19
|
+
async canAutoInstall() {
|
|
20
|
+
return proveGlobalNpmInstall({
|
|
21
|
+
currentVersion: options.currentVersion,
|
|
22
|
+
packageRoot: options.packageRoot,
|
|
23
|
+
npmCommand,
|
|
24
|
+
runner,
|
|
25
|
+
env,
|
|
26
|
+
timeoutMs,
|
|
27
|
+
});
|
|
28
|
+
},
|
|
29
|
+
async installExact(version) {
|
|
30
|
+
if (!isStrictRelease(version))
|
|
31
|
+
return { ok: false, reason: "invalid-version" };
|
|
32
|
+
const result = await runner(npmCommand, buildExactInstallArgs(version), { timeoutMs: Math.max(timeoutMs, 120000), env });
|
|
33
|
+
if (result.exitCode !== 0 || result.timedOut)
|
|
34
|
+
return { ok: false, reason: "install-failed" };
|
|
35
|
+
return { ok: true };
|
|
36
|
+
},
|
|
37
|
+
async verifyInstalled(version) {
|
|
38
|
+
const list = await runner(npmCommand, ["list", "-g", PACKAGE_NAME, "--depth=0", "--json"], { timeoutMs, env });
|
|
39
|
+
if (list.exitCode !== 0 || !verifyNpmListVersion(list.stdout, version)) {
|
|
40
|
+
return { ok: false, reason: "npm-list-version-mismatch" };
|
|
41
|
+
}
|
|
42
|
+
const pathVersion = await runner(loopAgentCommand, ["--version"], { timeoutMs, env });
|
|
43
|
+
if (pathVersion.exitCode !== 0 || !verifyPathVersion(pathVersion.stdout, version)) {
|
|
44
|
+
return { ok: false, reason: "path-version-mismatch" };
|
|
45
|
+
}
|
|
46
|
+
return { ok: true };
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export function parseNpmViewLatest(stdout) {
|
|
51
|
+
const trimmed = stdout.trim();
|
|
52
|
+
let value = trimmed;
|
|
53
|
+
try {
|
|
54
|
+
value = JSON.parse(trimmed);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// npm may emit a bare value under older versions or mocked tests.
|
|
58
|
+
}
|
|
59
|
+
if (typeof value !== "string")
|
|
60
|
+
return undefined;
|
|
61
|
+
return isStrictRelease(value) ? value : undefined;
|
|
62
|
+
}
|
|
63
|
+
export function buildExactInstallArgs(version) {
|
|
64
|
+
if (!isStrictRelease(version))
|
|
65
|
+
throw new Error(`invalid release version: ${version}`);
|
|
66
|
+
return ["install", "-g", `${PACKAGE_NAME}@${version}`, "--no-fund", "--no-audit"];
|
|
67
|
+
}
|
|
68
|
+
export function verifyNpmListVersion(stdout, expectedVersion) {
|
|
69
|
+
try {
|
|
70
|
+
const parsed = JSON.parse(stdout);
|
|
71
|
+
return parsed.dependencies?.[PACKAGE_NAME]?.version === expectedVersion;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
export function verifyPathVersion(stdout, expectedVersion) {
|
|
78
|
+
return stdout.trim() === expectedVersion;
|
|
79
|
+
}
|
|
80
|
+
async function proveGlobalNpmInstall(options) {
|
|
81
|
+
const root = await options.runner(options.npmCommand, ["root", "-g"], {
|
|
82
|
+
timeoutMs: options.timeoutMs,
|
|
83
|
+
env: options.env,
|
|
84
|
+
});
|
|
85
|
+
if (root.exitCode !== 0 || root.timedOut)
|
|
86
|
+
return { ok: false, reason: "npm-root-failed" };
|
|
87
|
+
const globalPackageRoot = path.join(root.stdout.trim(), "@tea-agent", "loop-agent");
|
|
88
|
+
try {
|
|
89
|
+
const [expectedReal, currentReal, expectedStat] = await Promise.all([
|
|
90
|
+
realpath(globalPackageRoot),
|
|
91
|
+
realpath(options.packageRoot),
|
|
92
|
+
lstat(globalPackageRoot),
|
|
93
|
+
]);
|
|
94
|
+
if (expectedStat.isSymbolicLink())
|
|
95
|
+
return { ok: false, reason: "linked-global-package" };
|
|
96
|
+
if (expectedReal !== currentReal)
|
|
97
|
+
return { ok: false, reason: "not-global" };
|
|
98
|
+
const pkg = JSON.parse(await readFile(path.join(options.packageRoot, "package.json"), "utf-8"));
|
|
99
|
+
if (pkg.name !== PACKAGE_NAME)
|
|
100
|
+
return { ok: false, reason: "wrong-package" };
|
|
101
|
+
if (pkg.version !== options.currentVersion)
|
|
102
|
+
return { ok: false, reason: "version-mismatch" };
|
|
103
|
+
return { ok: true };
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return { ok: false, reason: "source-proof-failed" };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function isStrictRelease(version) {
|
|
110
|
+
const parsed = semver.parse(version);
|
|
111
|
+
return Boolean(parsed && parsed.prerelease.length === 0);
|
|
112
|
+
}
|
|
113
|
+
async function runCommand(command, args, options) {
|
|
114
|
+
return new Promise((resolve) => {
|
|
115
|
+
const child = spawn(command, [...args], {
|
|
116
|
+
env: options.env ? { ...process.env, ...options.env } : process.env,
|
|
117
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
118
|
+
shell: false,
|
|
119
|
+
});
|
|
120
|
+
let stdout = "";
|
|
121
|
+
let stderr = "";
|
|
122
|
+
let settled = false;
|
|
123
|
+
const timer = setTimeout(() => {
|
|
124
|
+
settled = true;
|
|
125
|
+
child.kill("SIGTERM");
|
|
126
|
+
resolve({ exitCode: null, stdout, stderr, timedOut: true });
|
|
127
|
+
}, options.timeoutMs);
|
|
128
|
+
child.stdout.setEncoding("utf-8");
|
|
129
|
+
child.stderr.setEncoding("utf-8");
|
|
130
|
+
child.stdout.on("data", (chunk) => {
|
|
131
|
+
stdout += chunk;
|
|
132
|
+
});
|
|
133
|
+
child.stderr.on("data", (chunk) => {
|
|
134
|
+
stderr += chunk;
|
|
135
|
+
});
|
|
136
|
+
child.on("error", (error) => {
|
|
137
|
+
if (settled)
|
|
138
|
+
return;
|
|
139
|
+
settled = true;
|
|
140
|
+
clearTimeout(timer);
|
|
141
|
+
resolve({ exitCode: null, stdout, stderr: `${stderr}${error.message}`, timedOut: false });
|
|
142
|
+
});
|
|
143
|
+
child.on("close", (exitCode) => {
|
|
144
|
+
if (settled)
|
|
145
|
+
return;
|
|
146
|
+
settled = true;
|
|
147
|
+
clearTimeout(timer);
|
|
148
|
+
resolve({ exitCode, stdout, stderr, timedOut: false });
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import semver from "semver";
|
|
2
|
+
const CONTROLLER_SENSITIVE_COMMANDS = new Set([
|
|
3
|
+
"run-dag",
|
|
4
|
+
"dag",
|
|
5
|
+
"loop",
|
|
6
|
+
"delegate",
|
|
7
|
+
"pi-prompt",
|
|
8
|
+
"cursor-prompt",
|
|
9
|
+
]);
|
|
10
|
+
const MACHINE_OUTPUT_FLAGS = new Set(["--json", "--markdown"]);
|
|
11
|
+
export function isStrictReleaseVersion(version) {
|
|
12
|
+
if (!version)
|
|
13
|
+
return false;
|
|
14
|
+
const parsed = semver.parse(version);
|
|
15
|
+
return Boolean(parsed && parsed.prerelease.length === 0);
|
|
16
|
+
}
|
|
17
|
+
export function shouldCheckForUpdates(input) {
|
|
18
|
+
if (!input.stdinIsTTY || !input.stdoutIsTTY || !input.stderrIsTTY)
|
|
19
|
+
return false;
|
|
20
|
+
if (input.env.CI || input.env.LOOP_AGENT_DISABLE_UPDATE_CHECK === "1")
|
|
21
|
+
return false;
|
|
22
|
+
const args = input.argv;
|
|
23
|
+
if (args.some((arg) => arg === "--help" || arg === "-h" || arg === "--version" || arg === "-V")) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
if (args.some((arg) => MACHINE_OUTPUT_FLAGS.has(arg)))
|
|
27
|
+
return false;
|
|
28
|
+
const command = firstCommand(args);
|
|
29
|
+
if (command && CONTROLLER_SENSITIVE_COMMANDS.has(command))
|
|
30
|
+
return false;
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
export function evaluateLatestVersion(input) {
|
|
34
|
+
const now = input.now ?? new Date();
|
|
35
|
+
if (input.retryAfter && Date.parse(input.retryAfter) > now.getTime()) {
|
|
36
|
+
return { shouldPrompt: false, reason: "retry-after" };
|
|
37
|
+
}
|
|
38
|
+
if (!isStrictReleaseVersion(input.currentVersion)) {
|
|
39
|
+
return { shouldPrompt: false, reason: "invalid-current-version" };
|
|
40
|
+
}
|
|
41
|
+
if (!isStrictReleaseVersion(input.latestVersion)) {
|
|
42
|
+
return { shouldPrompt: false, reason: "invalid-latest-version" };
|
|
43
|
+
}
|
|
44
|
+
if (!semver.gt(input.latestVersion, input.currentVersion)) {
|
|
45
|
+
return { shouldPrompt: false, reason: "not-newer" };
|
|
46
|
+
}
|
|
47
|
+
if (input.dismissedVersion === input.latestVersion) {
|
|
48
|
+
return { shouldPrompt: false, reason: "dismissed-version" };
|
|
49
|
+
}
|
|
50
|
+
return { shouldPrompt: true, targetVersion: input.latestVersion };
|
|
51
|
+
}
|
|
52
|
+
function firstCommand(argv) {
|
|
53
|
+
for (const arg of argv) {
|
|
54
|
+
if (!arg.startsWith("-"))
|
|
55
|
+
return arg;
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export function updateStatePath(homeDir) {
|
|
4
|
+
return path.join(homeDir, ".loop-agent", "update-state.json");
|
|
5
|
+
}
|
|
6
|
+
export function updateLockPath(homeDir) {
|
|
7
|
+
return path.join(homeDir, ".loop-agent", "update.lock");
|
|
8
|
+
}
|
|
9
|
+
export async function readUpdateState(statePath) {
|
|
10
|
+
try {
|
|
11
|
+
const raw = await readFile(statePath, "utf-8");
|
|
12
|
+
const parsed = JSON.parse(raw);
|
|
13
|
+
if (parsed.schemaVersion !== 1)
|
|
14
|
+
return emptyState();
|
|
15
|
+
return {
|
|
16
|
+
schemaVersion: 1,
|
|
17
|
+
lastCheckedAt: stringOrUndefined(parsed.lastCheckedAt),
|
|
18
|
+
latestVersion: stringOrUndefined(parsed.latestVersion),
|
|
19
|
+
dismissedVersion: stringOrUndefined(parsed.dismissedVersion),
|
|
20
|
+
retryAfter: stringOrUndefined(parsed.retryAfter),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return emptyState();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export async function writeUpdateState(statePath, state) {
|
|
28
|
+
await mkdir(path.dirname(statePath), { recursive: true });
|
|
29
|
+
const tempPath = path.join(path.dirname(statePath), `.${path.basename(statePath)}.${process.pid}.${Date.now()}.tmp`);
|
|
30
|
+
await writeFile(tempPath, `${JSON.stringify({ ...state, schemaVersion: 1 }, null, 2)}\n`, "utf-8");
|
|
31
|
+
await rename(tempPath, statePath);
|
|
32
|
+
}
|
|
33
|
+
export async function acquireUpdateLock(lockPath, options = {}) {
|
|
34
|
+
const now = options.now ?? (() => new Date());
|
|
35
|
+
const staleMs = options.staleMs ?? 10 * 60 * 1000;
|
|
36
|
+
await mkdir(path.dirname(lockPath), { recursive: true });
|
|
37
|
+
try {
|
|
38
|
+
await mkdir(lockPath);
|
|
39
|
+
await writeFile(path.join(lockPath, "owner.json"), JSON.stringify({ pid: process.pid, at: now().toISOString() }), "utf-8");
|
|
40
|
+
return async () => {
|
|
41
|
+
await rm(lockPath, { recursive: true, force: true });
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
try {
|
|
46
|
+
const ownerRaw = await readFile(path.join(lockPath, "owner.json"), "utf-8");
|
|
47
|
+
const owner = JSON.parse(ownerRaw);
|
|
48
|
+
if (owner.at && now().getTime() - Date.parse(owner.at) > staleMs) {
|
|
49
|
+
await rm(lockPath, { recursive: true, force: true });
|
|
50
|
+
await mkdir(lockPath);
|
|
51
|
+
await writeFile(path.join(lockPath, "owner.json"), JSON.stringify({ pid: process.pid, at: now().toISOString() }), "utf-8");
|
|
52
|
+
return async () => {
|
|
53
|
+
await rm(lockPath, { recursive: true, force: true });
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function emptyState() {
|
|
64
|
+
return { schemaVersion: 1 };
|
|
65
|
+
}
|
|
66
|
+
function stringOrUndefined(value) {
|
|
67
|
+
return typeof value === "string" ? value : undefined;
|
|
68
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -1,12 +1,45 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
2
6
|
import { buildLoopAgentProgram } from "./cli/program.js";
|
|
7
|
+
import { createNpmUpdateClient } from "./cli/update/npm-client.js";
|
|
8
|
+
import { runSelfUpdateNotifier } from "./cli/update/notifier.js";
|
|
3
9
|
async function main() {
|
|
4
10
|
const program = buildLoopAgentProgram({
|
|
5
11
|
defaultRepoRoot: process.cwd(),
|
|
6
12
|
});
|
|
7
13
|
await program.parseAsync(process.argv);
|
|
14
|
+
if (process.exitCode && process.exitCode !== 0)
|
|
15
|
+
return;
|
|
16
|
+
const packageRoot = resolvePackageRoot();
|
|
17
|
+
const currentVersion = readPackageVersion(packageRoot);
|
|
18
|
+
await runSelfUpdateNotifier({
|
|
19
|
+
argv: process.argv.slice(2),
|
|
20
|
+
currentVersion,
|
|
21
|
+
env: process.env,
|
|
22
|
+
homeDir: os.homedir(),
|
|
23
|
+
stdinIsTTY: process.stdin.isTTY === true,
|
|
24
|
+
stdoutIsTTY: process.stdout.isTTY === true,
|
|
25
|
+
stderrIsTTY: process.stderr.isTTY === true,
|
|
26
|
+
client: createNpmUpdateClient({
|
|
27
|
+
currentVersion,
|
|
28
|
+
packageRoot,
|
|
29
|
+
env: process.env,
|
|
30
|
+
}),
|
|
31
|
+
});
|
|
8
32
|
}
|
|
9
33
|
main().catch((error) => {
|
|
10
34
|
console.error(error instanceof Error ? error.message : String(error));
|
|
11
35
|
process.exit(1);
|
|
12
36
|
});
|
|
37
|
+
function resolvePackageRoot() {
|
|
38
|
+
return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
39
|
+
}
|
|
40
|
+
function readPackageVersion(packageRoot) {
|
|
41
|
+
const pkg = JSON.parse(readFileSync(path.join(packageRoot, "package.json"), "utf-8"));
|
|
42
|
+
if (!pkg.version)
|
|
43
|
+
throw new Error("loop-agent package.json is missing version");
|
|
44
|
+
return pkg.version;
|
|
45
|
+
}
|