@vimhead.dev/norn-cli 0.1.0-tip.35614826665.1 → 0.1.0-tip.35724621678.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.
Files changed (119) hide show
  1. package/assets/README.md +41 -13
  2. package/assets/docs/README.md +16 -16
  3. package/assets/docs/agents.md +23 -15
  4. package/assets/docs/cli.md +10 -8
  5. package/assets/docs/composition.md +7 -7
  6. package/assets/docs/persistence.md +22 -22
  7. package/assets/docs/projects.md +6 -6
  8. package/assets/docs/recovery.md +10 -10
  9. package/assets/docs/schemas.md +42 -36
  10. package/assets/docs/workflows.md +39 -32
  11. package/assets/examples/agent-then-analysis/input.json +3 -3
  12. package/assets/examples/agent-then-analysis/norn.project.json +2 -2
  13. package/assets/examples/agent-then-analysis/plugin.ts +37 -9
  14. package/assets/examples/caller-owned-routing/README.md +6 -6
  15. package/assets/examples/caller-owned-routing/assessment.ts +18 -4
  16. package/assets/examples/caller-owned-routing/contracts.ts +7 -2
  17. package/assets/examples/caller-owned-routing/input.json +12 -12
  18. package/assets/examples/caller-owned-routing/norn.project.json +2 -2
  19. package/assets/examples/caller-owned-routing/revision.ts +7 -2
  20. package/assets/examples/caller-owned-routing/router.ts +14 -3
  21. package/assets/examples/caller-selected-continuation/caller.ts +38 -12
  22. package/assets/examples/caller-selected-continuation/input.json +7 -7
  23. package/assets/examples/caller-selected-continuation/norn.project.json +2 -2
  24. package/assets/examples/caller-selected-continuation/producer.ts +10 -3
  25. package/assets/examples/coordinating-multiple-agents/README.md +6 -6
  26. package/assets/examples/coordinating-multiple-agents/input.json +20 -8
  27. package/assets/examples/coordinating-multiple-agents/norn.project.json +2 -2
  28. package/assets/examples/coordinating-multiple-agents/plugin.ts +165 -46
  29. package/assets/examples/coordinating-multiple-agents/queue-tools.ts +30 -7
  30. package/assets/examples/coordinating-multiple-agents/work-queue.ts +211 -63
  31. package/assets/examples/getting-started/norn.project.json +2 -2
  32. package/assets/examples/getting-started/plugin.ts +33 -13
  33. package/assets/examples/minimal-workflow/norn.project.json +2 -2
  34. package/assets/examples/minimal-workflow/plugin.ts +12 -3
  35. package/assets/examples/shared-state/README.md +6 -6
  36. package/assets/examples/shared-state/input.json +1 -1
  37. package/assets/examples/shared-state/norn.project.json +2 -2
  38. package/assets/examples/shared-state/plugin.ts +44 -15
  39. package/assets/examples/shared-state/shared-state.ts +46 -13
  40. package/assets/examples/shared-state/state-tools.ts +68 -16
  41. package/assets/examples/worktree-development-loop/README.md +12 -12
  42. package/assets/examples/worktree-development-loop/norn.project.json +7 -7
  43. package/assets/examples/worktree-development-loop/plugin.ts +7 -1
  44. package/assets/examples/worktree-development-loop/scope.ts +4 -1
  45. package/assets/examples/worktree-development-loop/shared/commands.ts +6 -2
  46. package/assets/examples/worktree-development-loop/workflows/development-loop/execute.ts +18 -4
  47. package/assets/examples/worktree-development-loop/workflows/development-loop/index.ts +4 -1
  48. package/assets/examples/worktree-development-loop/workflows/development-loop/repository.ts +11 -1
  49. package/assets/examples/worktree-development-loop/workflows/development-loop/schema.ts +6 -2
  50. package/assets/examples/worktree-development-loop/workflows/implementation/execute.ts +40 -10
  51. package/assets/examples/worktree-development-loop/workflows/implementation/index.ts +5 -1
  52. package/assets/examples/worktree-development-loop/workflows/planning/execute.ts +1 -1
  53. package/assets/examples/worktree-development-loop/workflows/planning/index.ts +5 -1
  54. package/assets/examples/worktree-development-loop/workflows/review/execute.ts +33 -9
  55. package/assets/examples/worktree-development-loop/workflows/review-router/execute.ts +51 -8
  56. package/assets/package.json +1 -1
  57. package/assets/packages/cli/src/build-info.ts +4 -1
  58. package/assets/packages/cli/src/bun/cli.ts +21 -5
  59. package/assets/packages/cli/src/cli.ts +585 -170
  60. package/assets/packages/cli/src/client.ts +194 -36
  61. package/assets/packages/cli/src/documentation.ts +135 -30
  62. package/assets/packages/cli/src/generated-build-info.ts +2 -2
  63. package/assets/packages/cli/src/internal/agent-directory.ts +7 -2
  64. package/assets/packages/cli/src/internal/agent-response-tool.ts +57 -15
  65. package/assets/packages/cli/src/internal/agents.ts +248 -60
  66. package/assets/packages/cli/src/internal/commands.ts +82 -20
  67. package/assets/packages/cli/src/internal/documentation-bundle.ts +44 -13
  68. package/assets/packages/cli/src/internal/engine.ts +396 -87
  69. package/assets/packages/cli/src/internal/errors.ts +13 -5
  70. package/assets/packages/cli/src/internal/execution-context.ts +20 -12
  71. package/assets/packages/cli/src/internal/file-coordinator.ts +95 -26
  72. package/assets/packages/cli/src/internal/file-names.ts +4 -1
  73. package/assets/packages/cli/src/internal/launch-request.ts +74 -22
  74. package/assets/packages/cli/src/internal/logs.ts +10 -2
  75. package/assets/packages/cli/src/internal/metrics.ts +247 -61
  76. package/assets/packages/cli/src/internal/pi-assets.ts +105 -23
  77. package/assets/packages/cli/src/internal/run-lease.ts +72 -22
  78. package/assets/packages/cli/src/internal/run-log.ts +49 -20
  79. package/assets/packages/cli/src/internal/run-names.ts +120 -12
  80. package/assets/packages/cli/src/internal/run-state.ts +306 -71
  81. package/assets/packages/cli/src/internal/run-store.ts +216 -53
  82. package/assets/packages/cli/src/internal/usage.ts +23 -7
  83. package/assets/packages/cli/src/internal/worker-directory.ts +9 -3
  84. package/assets/packages/cli/src/internal/workflow-registry.ts +281 -70
  85. package/assets/packages/cli/src/internal/working-directory.ts +2 -1
  86. package/assets/packages/cli/src/workflow-loader.ts +341 -74
  87. package/assets/packages/core/src/atomic-files.ts +13 -4
  88. package/assets/packages/core/src/workflow-transition.ts +13 -2
  89. package/assets/packages/sdk/src/api.ts +248 -80
  90. package/assets/packages/sdk/src/index.ts +6 -1
  91. package/assets/packages/sdk/src/schema.ts +75 -21
  92. package/assets/tests/workflow-ref.test.ts +242 -57
  93. package/bin/norn.mjs +13 -4
  94. package/dist/cli.js +344 -90
  95. package/dist/client.js +100 -22
  96. package/dist/documentation.js +102 -25
  97. package/dist/generated-build-info.d.ts +2 -2
  98. package/dist/generated-build-info.js +2 -2
  99. package/dist/internal/agent-directory.js +3 -1
  100. package/dist/internal/agent-response-tool.js +42 -11
  101. package/dist/internal/agents.js +149 -37
  102. package/dist/internal/commands.js +65 -16
  103. package/dist/internal/documentation-bundle.js +23 -11
  104. package/dist/internal/engine.js +304 -66
  105. package/dist/internal/errors.js +4 -2
  106. package/dist/internal/execution-context.js +2 -1
  107. package/dist/internal/file-coordinator.js +59 -19
  108. package/dist/internal/launch-request.js +32 -14
  109. package/dist/internal/metrics.js +138 -38
  110. package/dist/internal/pi-assets.js +70 -19
  111. package/dist/internal/run-lease.js +29 -9
  112. package/dist/internal/run-log.js +24 -12
  113. package/dist/internal/run-state.js +172 -48
  114. package/dist/internal/run-store.js +157 -45
  115. package/dist/internal/worker-directory.js +6 -2
  116. package/dist/internal/workflow-registry.js +123 -32
  117. package/dist/internal/working-directory.js +2 -1
  118. package/dist/workflow-loader.js +198 -41
  119. package/package.json +2 -2
@@ -30,22 +30,38 @@ async function writeTextAtomically(path, content) {
30
30
 
31
31
  // src/internal/file-coordinator.ts
32
32
  import { createHash, randomUUID as randomUUID2 } from "node:crypto";
33
- import { lstat, mkdir as mkdir2, mkdtemp, readFile, readdir, realpath, rename as rename2, rm as rm2, rmdir, unlink, writeFile as writeFile2 } from "node:fs/promises";
33
+ import {
34
+ lstat,
35
+ mkdir as mkdir2,
36
+ mkdtemp,
37
+ readFile,
38
+ readdir,
39
+ realpath,
40
+ rename as rename2,
41
+ rm as rm2,
42
+ rmdir,
43
+ unlink,
44
+ writeFile as writeFile2
45
+ } from "node:fs/promises";
34
46
  import { hostname } from "node:os";
35
47
  import { basename, dirname as dirname2, join, resolve } from "node:path";
36
48
  import { setTimeout as delay } from "node:timers/promises";
37
49
  import { Type } from "typebox";
38
50
  import { Value } from "typebox/value";
39
- var ownerSchema = Type.Object({
40
- token: Type.String({ format: "uuid" }),
41
- pid: Type.Integer({ exclusiveMinimum: 0 }),
42
- host: Type.String(),
43
- target: Type.String()
44
- }, { additionalProperties: false });
51
+ var ownerSchema = Type.Object(
52
+ {
53
+ token: Type.String({ format: "uuid" }),
54
+ pid: Type.Integer({ exclusiveMinimum: 0 }),
55
+ host: Type.String(),
56
+ target: Type.String()
57
+ },
58
+ { additionalProperties: false }
59
+ );
45
60
  var NornFileCoordinator = class {
46
61
  constructor(input) {
47
62
  this.input = input;
48
- if (!Number.isSafeInteger(input.waitTimeoutMs) || input.waitTimeoutMs <= 0) throw new Error("Lock wait timeout must be a positive integer");
63
+ if (!Number.isSafeInteger(input.waitTimeoutMs) || input.waitTimeoutMs <= 0)
64
+ throw new Error("Lock wait timeout must be a positive integer");
49
65
  this.lockRoot = resolve(input.lockRoot);
50
66
  }
51
67
  input;
@@ -60,7 +76,11 @@ var NornFileCoordinator = class {
60
76
  const marker = `${token}.json`;
61
77
  return this.withCleanup({
62
78
  operation: async () => {
63
- await writeFile2(join(stagingPath, marker), JSON.stringify({ token, pid: process.pid, host: hostname(), target }), { mode: 384 });
79
+ await writeFile2(
80
+ join(stagingPath, marker),
81
+ JSON.stringify({ token, pid: process.pid, host: hostname(), target }),
82
+ { mode: 384 }
83
+ );
64
84
  await this.acquire({ stagingPath, lockPath, target });
65
85
  return this.withCleanup({
66
86
  operation: () => operation(target),
@@ -74,11 +94,17 @@ var NornFileCoordinator = class {
74
94
  });
75
95
  }
76
96
  async readText(path) {
77
- return this.withExclusiveLock(path, (lockedPath) => readFile(lockedPath, "utf8"));
97
+ return this.withExclusiveLock(
98
+ path,
99
+ (lockedPath) => readFile(lockedPath, "utf8")
100
+ );
78
101
  }
79
102
  async writeText(path, content) {
80
103
  await mkdir2(dirname2(path), { recursive: true });
81
- await this.withExclusiveLock(path, (lockedPath) => writeTextAtomically(lockedPath, content));
104
+ await this.withExclusiveLock(
105
+ path,
106
+ (lockedPath) => writeTextAtomically(lockedPath, content)
107
+ );
82
108
  }
83
109
  async withCleanup(input) {
84
110
  let result;
@@ -88,7 +114,10 @@ var NornFileCoordinator = class {
88
114
  try {
89
115
  await input.cleanup();
90
116
  } catch (cleanupError) {
91
- throw new AggregateError([error, cleanupError], "File operation and lock cleanup failed");
117
+ throw new AggregateError(
118
+ [error, cleanupError],
119
+ "File operation and lock cleanup failed"
120
+ );
92
121
  }
93
122
  throw error;
94
123
  }
@@ -104,7 +133,8 @@ var NornFileCoordinator = class {
104
133
  if (isNodeError(error2) && error2.code === "ENOENT") return void 0;
105
134
  throw error2;
106
135
  });
107
- if (entry?.isSymbolicLink()) throw new Error(`Cannot lock a dangling symbolic link: ${path}`);
136
+ if (entry?.isSymbolicLink())
137
+ throw new Error(`Cannot lock a dangling symbolic link: ${path}`);
108
138
  return join(await realpath(dirname2(path)), basename(path));
109
139
  }
110
140
  }
@@ -115,10 +145,12 @@ var NornFileCoordinator = class {
115
145
  await rename2(input.stagingPath, input.lockPath);
116
146
  return;
117
147
  } catch (error) {
118
- if (!isNodeError(error) || !["ENOTEMPTY", "EEXIST"].includes(error.code ?? "")) throw error;
148
+ if (!isNodeError(error) || !["ENOTEMPTY", "EEXIST"].includes(error.code ?? ""))
149
+ throw error;
119
150
  }
120
151
  await this.reclaimDeadOwner(input);
121
- if (Date.now() >= deadline) throw new Error(`Timed out waiting for file lock: ${input.target}`);
152
+ if (Date.now() >= deadline)
153
+ throw new Error(`Timed out waiting for file lock: ${input.target}`);
122
154
  await delay(10);
123
155
  }
124
156
  }
@@ -126,9 +158,13 @@ var NornFileCoordinator = class {
126
158
  try {
127
159
  const entries = await readdir(input.lockPath);
128
160
  if (entries.length === 0) return;
129
- if (entries.length !== 1) throw new Error(`Invalid file lock ownership: ${input.lockPath}`);
161
+ if (entries.length !== 1)
162
+ throw new Error(`Invalid file lock ownership: ${input.lockPath}`);
130
163
  const marker = entries[0];
131
- const owner = Value.Parse(ownerSchema, JSON.parse(await readFile(join(input.lockPath, marker), "utf8")));
164
+ const owner = Value.Parse(
165
+ ownerSchema,
166
+ JSON.parse(await readFile(join(input.lockPath, marker), "utf8"))
167
+ );
132
168
  if (marker !== `${owner.token}.json` || owner.target !== input.target || owner.host !== hostname()) {
133
169
  throw new Error(`Incompatible file lock ownership: ${input.lockPath}`);
134
170
  }
@@ -149,12 +185,16 @@ var NornFileCoordinator = class {
149
185
  try {
150
186
  await rmdir(path);
151
187
  } catch (error) {
152
- if (!isNodeError(error) || !["ENOENT", "ENOTEMPTY", "EEXIST"].includes(error.code ?? "")) throw error;
188
+ if (!isNodeError(error) || !["ENOENT", "ENOTEMPTY", "EEXIST"].includes(error.code ?? ""))
189
+ throw error;
153
190
  }
154
191
  }
155
192
  };
156
193
  function createRunFileCoordinator(runRoot) {
157
- return new NornFileCoordinator({ lockRoot: join(runRoot, "locks"), waitTimeoutMs: 3e4 });
194
+ return new NornFileCoordinator({
195
+ lockRoot: join(runRoot, "locks"),
196
+ waitTimeoutMs: 3e4
197
+ });
158
198
  }
159
199
  export {
160
200
  NornFileCoordinator,
@@ -45,7 +45,9 @@ async function writeRunLaunchRequest(runRoot, request) {
45
45
  await writeJsonAtomically(join(runRoot, LAUNCH_REQUEST_FILE_NAME), request);
46
46
  }
47
47
  async function readRunLaunchRequest(runRoot) {
48
- return parseRunLaunchRequest(JSON.parse(await readFile(join(runRoot, LAUNCH_REQUEST_FILE_NAME), "utf8")));
48
+ return parseRunLaunchRequest(
49
+ JSON.parse(await readFile(join(runRoot, LAUNCH_REQUEST_FILE_NAME), "utf8"))
50
+ );
49
51
  }
50
52
  async function writeRunResumeRequest(runRoot, request) {
51
53
  const path = join(runRoot, RESUME_REQUEST_FILE_NAME);
@@ -58,7 +60,9 @@ async function writeRunResumeRequest(runRoot, request) {
58
60
  }
59
61
  }
60
62
  async function readRunResumeRequest(runRoot) {
61
- return parseRunResumeRequest(JSON.parse(await readFile(join(runRoot, RESUME_REQUEST_FILE_NAME), "utf8")));
63
+ return parseRunResumeRequest(
64
+ JSON.parse(await readFile(join(runRoot, RESUME_REQUEST_FILE_NAME), "utf8"))
65
+ );
62
66
  }
63
67
  async function readOptionalRunResumeRequest(runRoot) {
64
68
  try {
@@ -73,27 +77,41 @@ function matchesRunResumeRequest(actual, expected) {
73
77
  }
74
78
  async function clearRunResumeRequest(input) {
75
79
  await input.lease.assertOwned();
76
- if (matchesRunResumeRequest(await readOptionalRunResumeRequest(input.runRoot), input.request)) {
80
+ if (matchesRunResumeRequest(
81
+ await readOptionalRunResumeRequest(input.runRoot),
82
+ input.request
83
+ )) {
77
84
  await rm2(join(input.runRoot, RESUME_REQUEST_FILE_NAME), { force: true });
78
85
  }
79
86
  }
80
87
  function parseRunLaunchRequest(value) {
81
- if (!value || typeof value !== "object") throw new Error("Invalid workflow launch request");
88
+ if (!value || typeof value !== "object")
89
+ throw new Error("Invalid workflow launch request");
82
90
  const request = value;
83
- if (request.version !== 2 || request.type !== "run") throw new Error("Unsupported workflow launch request");
84
- if (typeof request.id !== "string" || request.id.length === 0) throw new Error("Invalid workflow launch request id");
85
- if (typeof request.name !== "string" || request.name.length === 0) throw new Error("Invalid workflow launch request name");
86
- if (typeof request.workflowId !== "string" || request.workflowId.length === 0) throw new Error("Invalid workflow launch request workflow id");
87
- if (typeof request.createdAt !== "string" || Number.isNaN(Date.parse(request.createdAt))) throw new Error("Invalid workflow launch request timestamp");
91
+ if (request.version !== 2 || request.type !== "run")
92
+ throw new Error("Unsupported workflow launch request");
93
+ if (typeof request.id !== "string" || request.id.length === 0)
94
+ throw new Error("Invalid workflow launch request id");
95
+ if (typeof request.name !== "string" || request.name.length === 0)
96
+ throw new Error("Invalid workflow launch request name");
97
+ if (typeof request.workflowId !== "string" || request.workflowId.length === 0)
98
+ throw new Error("Invalid workflow launch request workflow id");
99
+ if (typeof request.createdAt !== "string" || Number.isNaN(Date.parse(request.createdAt)))
100
+ throw new Error("Invalid workflow launch request timestamp");
88
101
  return request;
89
102
  }
90
103
  function parseRunResumeRequest(value) {
91
- if (!value || typeof value !== "object") throw new Error("Invalid workflow resume request");
104
+ if (!value || typeof value !== "object")
105
+ throw new Error("Invalid workflow resume request");
92
106
  const request = value;
93
- if (request.version !== 2 || request.type !== "resume") throw new Error("Unsupported workflow resume request");
94
- if (typeof request.id !== "string" || request.id.length === 0) throw new Error("Invalid workflow resume request id");
95
- if (request.requestId !== void 0 && (typeof request.requestId !== "string" || request.requestId.length === 0)) throw new Error("Invalid workflow resume request token");
96
- if (typeof request.createdAt !== "string" || Number.isNaN(Date.parse(request.createdAt))) throw new Error("Invalid workflow resume request timestamp");
107
+ if (request.version !== 2 || request.type !== "resume")
108
+ throw new Error("Unsupported workflow resume request");
109
+ if (typeof request.id !== "string" || request.id.length === 0)
110
+ throw new Error("Invalid workflow resume request id");
111
+ if (request.requestId !== void 0 && (typeof request.requestId !== "string" || request.requestId.length === 0))
112
+ throw new Error("Invalid workflow resume request token");
113
+ if (typeof request.createdAt !== "string" || Number.isNaN(Date.parse(request.createdAt)))
114
+ throw new Error("Invalid workflow resume request timestamp");
97
115
  return request;
98
116
  }
99
117
  export {
@@ -10,7 +10,11 @@ function isNodeError(error) {
10
10
  }
11
11
 
12
12
  // src/internal/metrics.ts
13
- import { addAgentUsage, agentUsageFromValue, emptyAgentUsage } from "./usage.js";
13
+ import {
14
+ addAgentUsage,
15
+ agentUsageFromValue,
16
+ emptyAgentUsage
17
+ } from "./usage.js";
14
18
  var LEGACY_RUN_STATE_FILE_NAME = "runtime-state.json";
15
19
  async function readRunMetrics(runRoot) {
16
20
  const currentRoot = runCurrentRoot(runRoot);
@@ -32,7 +36,8 @@ function calculateRunMetrics(events, state, now) {
32
36
  let agentUsage = emptyAgentUsage();
33
37
  for (const event of events) {
34
38
  const eventAtMs = timestampMs(event.at);
35
- if (event.type === "run.interrupted" && eventAtMs !== void 0) gateStartedAtMs = eventAtMs;
39
+ if (event.type === "run.interrupted" && eventAtMs !== void 0)
40
+ gateStartedAtMs = eventAtMs;
36
41
  else if (event.type === "run.resumed" && eventAtMs !== void 0 && gateStartedAtMs !== void 0) {
37
42
  gateWaitMs += Math.max(0, eventAtMs - gateStartedAtMs);
38
43
  gateStartedAtMs = void 0;
@@ -50,40 +55,87 @@ function calculateRunMetrics(events, state, now) {
50
55
  continue;
51
56
  }
52
57
  if (event.type === "agent.started") {
53
- if (openWorkflow) openWorkflow = startAgentMetrics(openWorkflow, event, now);
58
+ if (openWorkflow)
59
+ openWorkflow = startAgentMetrics(openWorkflow, event, now);
54
60
  continue;
55
61
  }
56
62
  if (event.type === "agent.completed" || event.type === "agent.failed") {
57
63
  const usage = agentUsageFromEvent(event);
58
64
  agentUsage = addAgentUsage(agentUsage, usage);
59
- if (openWorkflow) openWorkflow = finishAgentMetrics(openWorkflow, event, usage, now);
65
+ if (openWorkflow)
66
+ openWorkflow = finishAgentMetrics(openWorkflow, event, usage, now);
60
67
  continue;
61
68
  }
62
69
  if (event.type === "command.started") {
63
- if (openWorkflow) openWorkflow = startCommandMetrics(openWorkflow, event, now);
70
+ if (openWorkflow)
71
+ openWorkflow = startCommandMetrics(openWorkflow, event, now);
64
72
  continue;
65
73
  }
66
74
  if (event.type === "command.completed" || event.type === "command.failed") {
67
- if (openWorkflow) openWorkflow = finishCommandMetrics(openWorkflow, event, now);
75
+ if (openWorkflow)
76
+ openWorkflow = finishCommandMetrics(openWorkflow, event, now);
68
77
  continue;
69
78
  }
70
79
  const status = workflowTerminalStatus(event.type);
71
80
  if (status) {
72
81
  const durationMs = numberField(event.durationMs) || (openWorkflow && eventAtMs !== void 0 ? Math.max(0, eventAtMs - openWorkflow.startedAtMs) : 0);
73
- const workflowId = status === "transitioned" ? stringField(event.fromWorkflowId, openWorkflow?.workflowId ?? "unknown") : stringField(event.workflowId, openWorkflow?.workflowId ?? "unknown");
74
- const workflow = openWorkflow ?? createSyntheticWorkflowMetrics(workflowId, startedAtMs, endedAtMs, eventAtMs, durationMs);
82
+ const workflowId = status === "transitioned" ? stringField(
83
+ event.fromWorkflowId,
84
+ openWorkflow?.workflowId ?? "unknown"
85
+ ) : stringField(
86
+ event.workflowId,
87
+ openWorkflow?.workflowId ?? "unknown"
88
+ );
89
+ const workflow = openWorkflow ?? createSyntheticWorkflowMetrics(
90
+ workflowId,
91
+ startedAtMs,
92
+ endedAtMs,
93
+ eventAtMs,
94
+ durationMs
95
+ );
75
96
  const childStatus = status === "failed" ? "failed" : "completed";
76
- workflows.push(closeWorkflowMetrics(closeOpenChildMetrics(workflow, eventAtMs ?? now.getTime(), event.at, childStatus), workflows.length + 1, status, durationMs, event.at));
97
+ workflows.push(
98
+ closeWorkflowMetrics(
99
+ closeOpenChildMetrics(
100
+ workflow,
101
+ eventAtMs ?? now.getTime(),
102
+ event.at,
103
+ childStatus
104
+ ),
105
+ workflows.length + 1,
106
+ status,
107
+ durationMs,
108
+ event.at
109
+ )
110
+ );
77
111
  openWorkflow = void 0;
78
112
  }
79
113
  }
80
- if (gateStartedAtMs !== void 0) gateWaitMs += Math.max(0, endedAtMs - gateStartedAtMs);
114
+ if (gateStartedAtMs !== void 0)
115
+ gateWaitMs += Math.max(0, endedAtMs - gateStartedAtMs);
81
116
  if (openWorkflow) {
82
- workflows.push(closeWorkflowMetrics(closeOpenChildMetrics(openWorkflow, endedAtMs, endedAt, "running"), workflows.length + 1, "running", Math.max(0, endedAtMs - openWorkflow.startedAtMs), endedAt));
117
+ workflows.push(
118
+ closeWorkflowMetrics(
119
+ closeOpenChildMetrics(openWorkflow, endedAtMs, endedAt, "running"),
120
+ workflows.length + 1,
121
+ "running",
122
+ Math.max(0, endedAtMs - openWorkflow.startedAtMs),
123
+ endedAt
124
+ )
125
+ );
83
126
  }
84
- const workflowsMs = workflows.reduce((sum, workflow) => sum + workflow.wallMs, 0);
85
- const agentsMs = workflows.reduce((sum, workflow) => sum + workflow.agentsMs, 0);
86
- const commandsMs = workflows.reduce((sum, workflow) => sum + workflow.commandsMs, 0);
127
+ const workflowsMs = workflows.reduce(
128
+ (sum, workflow) => sum + workflow.wallMs,
129
+ 0
130
+ );
131
+ const agentsMs = workflows.reduce(
132
+ (sum, workflow) => sum + workflow.agentsMs,
133
+ 0
134
+ );
135
+ const commandsMs = workflows.reduce(
136
+ (sum, workflow) => sum + workflow.commandsMs,
137
+ 0
138
+ );
87
139
  const wallMs = Math.max(0, endedAtMs - startedAtMs);
88
140
  return {
89
141
  status: state.status ?? "running",
@@ -102,10 +154,14 @@ function calculateRunMetrics(events, state, now) {
102
154
  }
103
155
  async function readRunStateSnapshot(currentRoot) {
104
156
  try {
105
- return JSON.parse(await readFile(join(currentRoot, RUN_STATE_FILE_NAME), "utf8"));
157
+ return JSON.parse(
158
+ await readFile(join(currentRoot, RUN_STATE_FILE_NAME), "utf8")
159
+ );
106
160
  } catch (error) {
107
161
  if (!isNodeError(error) || error.code !== "ENOENT") throw error;
108
- return JSON.parse(await readFile(join(currentRoot, LEGACY_RUN_STATE_FILE_NAME), "utf8"));
162
+ return JSON.parse(
163
+ await readFile(join(currentRoot, LEGACY_RUN_STATE_FILE_NAME), "utf8")
164
+ );
109
165
  }
110
166
  }
111
167
  async function readManifestEvents(path) {
@@ -127,32 +183,59 @@ function createSyntheticWorkflowMetrics(workflowId, runStartedAtMs, runEndedAtMs
127
183
  function startAgentMetrics(workflow, event, now) {
128
184
  return {
129
185
  ...workflow,
130
- openAgents: [...workflow.openAgents, {
131
- label: stringField(event.label, "unknown"),
132
- startedAt: isoTime(event.at, now),
133
- startedAtMs: timestampMs(event.at) ?? now.getTime()
134
- }]
186
+ openAgents: [
187
+ ...workflow.openAgents,
188
+ {
189
+ label: stringField(event.label, "unknown"),
190
+ startedAt: isoTime(event.at, now),
191
+ startedAtMs: timestampMs(event.at) ?? now.getTime()
192
+ }
193
+ ]
135
194
  };
136
195
  }
137
196
  function finishAgentMetrics(workflow, event, usage, now) {
138
- const [agent, openAgents] = takeOpenChild(workflow.openAgents, stringField(event.label, "unknown"));
139
- const closedAgent = closeAgentMetrics(event, workflow.agents.length + 1, usage, now, agent);
197
+ const [agent, openAgents] = takeOpenChild(
198
+ workflow.openAgents,
199
+ stringField(event.label, "unknown")
200
+ );
201
+ const closedAgent = closeAgentMetrics(
202
+ event,
203
+ workflow.agents.length + 1,
204
+ usage,
205
+ now,
206
+ agent
207
+ );
140
208
  return { ...workflow, agents: [...workflow.agents, closedAgent], openAgents };
141
209
  }
142
210
  function startCommandMetrics(workflow, event, now) {
143
211
  return {
144
212
  ...workflow,
145
- openCommands: [...workflow.openCommands, {
146
- label: stringField(event.label, "unknown"),
147
- startedAt: isoTime(event.at, now),
148
- startedAtMs: timestampMs(event.at) ?? now.getTime()
149
- }]
213
+ openCommands: [
214
+ ...workflow.openCommands,
215
+ {
216
+ label: stringField(event.label, "unknown"),
217
+ startedAt: isoTime(event.at, now),
218
+ startedAtMs: timestampMs(event.at) ?? now.getTime()
219
+ }
220
+ ]
150
221
  };
151
222
  }
152
223
  function finishCommandMetrics(workflow, event, now) {
153
- const [command, openCommands] = takeOpenChild(workflow.openCommands, stringField(event.label, "unknown"));
154
- const closedCommand = closeCommandMetrics(event, workflow.commands.length + 1, now, command);
155
- return { ...workflow, commands: [...workflow.commands, closedCommand], openCommands };
224
+ const [command, openCommands] = takeOpenChild(
225
+ workflow.openCommands,
226
+ stringField(event.label, "unknown")
227
+ );
228
+ const closedCommand = closeCommandMetrics(
229
+ event,
230
+ workflow.commands.length + 1,
231
+ now,
232
+ command
233
+ );
234
+ return {
235
+ ...workflow,
236
+ commands: [...workflow.commands, closedCommand],
237
+ openCommands
238
+ };
156
239
  }
157
240
  function closeOpenChildMetrics(workflow, endedAtMs, endedAt, status) {
158
241
  return {
@@ -185,8 +268,14 @@ function closeOpenChildMetrics(workflow, endedAtMs, endedAt, status) {
185
268
  };
186
269
  }
187
270
  function closeWorkflowMetrics(workflow, index, status, wallMs, endedAt) {
188
- const agentsMs = workflow.agents.reduce((sum, agent) => sum + agent.wallMs, 0);
189
- const commandsMs = workflow.commands.reduce((sum, command) => sum + command.wallMs, 0);
271
+ const agentsMs = workflow.agents.reduce(
272
+ (sum, agent) => sum + agent.wallMs,
273
+ 0
274
+ );
275
+ const commandsMs = workflow.commands.reduce(
276
+ (sum, command) => sum + command.wallMs,
277
+ 0
278
+ );
190
279
  return {
191
280
  index,
192
281
  workflowId: workflow.workflowId,
@@ -197,7 +286,10 @@ function closeWorkflowMetrics(workflow, index, status, wallMs, endedAt) {
197
286
  ownMs: Math.max(0, wallMs - agentsMs - commandsMs),
198
287
  agentsMs,
199
288
  commandsMs,
200
- agentUsage: workflow.agents.reduce((sum, agent) => addAgentUsage(sum, agent.usage), emptyAgentUsage()),
289
+ agentUsage: workflow.agents.reduce(
290
+ (sum, agent) => addAgentUsage(sum, agent.usage),
291
+ emptyAgentUsage()
292
+ ),
201
293
  agents: workflow.agents,
202
294
  commands: workflow.commands
203
295
  };
@@ -226,13 +318,19 @@ function closeCommandMetrics(event, index, now, openCommand) {
226
318
  startedAt: openCommand?.startedAt ?? isoTimeFromMs(Math.max(0, endedAtMs - wallMs)),
227
319
  endedAt: isoTime(event.at, now),
228
320
  wallMs,
229
- ...event.type === "command.completed" ? { exitCode: nullableNumberField(event.exitCode), killed: booleanField(event.killed) } : {}
321
+ ...event.type === "command.completed" ? {
322
+ exitCode: nullableNumberField(event.exitCode),
323
+ killed: booleanField(event.killed)
324
+ } : {}
230
325
  };
231
326
  }
232
327
  function takeOpenChild(children, label) {
233
328
  const index = children.findIndex((child) => child.label === label);
234
329
  if (index === -1) return [void 0, children];
235
- return [children[index], [...children.slice(0, index), ...children.slice(index + 1)]];
330
+ return [
331
+ children[index],
332
+ [...children.slice(0, index), ...children.slice(index + 1)]
333
+ ];
236
334
  }
237
335
  function workflowTerminalStatus(type) {
238
336
  if (type === "workflow.completed") return "completed";
@@ -241,8 +339,10 @@ function workflowTerminalStatus(type) {
241
339
  return void 0;
242
340
  }
243
341
  function runEndedAt(state) {
244
- if (state.status === "completed") return state.outcome?.completedAt ?? state.updatedAt;
245
- if (state.status === "failed") return state.failed?.failedAt ?? state.outcome?.completedAt ?? state.updatedAt;
342
+ if (state.status === "completed")
343
+ return state.outcome?.completedAt ?? state.updatedAt;
344
+ if (state.status === "failed")
345
+ return state.failed?.failedAt ?? state.outcome?.completedAt ?? state.updatedAt;
246
346
  return void 0;
247
347
  }
248
348
  function agentUsageFromEvent(event) {
@@ -1,6 +1,15 @@
1
1
  // src/internal/pi-assets.ts
2
2
  import { createHash } from "node:crypto";
3
- import { lstat, mkdir, mkdtemp, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
3
+ import {
4
+ lstat,
5
+ mkdir,
6
+ mkdtemp,
7
+ readFile,
8
+ readdir,
9
+ rename,
10
+ rm,
11
+ writeFile
12
+ } from "node:fs/promises";
4
13
  import { join, resolve } from "node:path";
5
14
  import { gunzipSync } from "node:zlib";
6
15
 
@@ -11,15 +20,28 @@ function isNodeError(error) {
11
20
 
12
21
  // src/internal/pi-assets.ts
13
22
  function resolvePiAssetsCacheRoot(input) {
14
- if (input.environment.NORN_PI_CACHE_DIR) return resolve(input.environment.NORN_PI_CACHE_DIR);
15
- if (input.platform === "darwin") return join(input.home, "Library", "Caches", "norn", "pi");
16
- if (input.platform === "win32") return join(input.environment.LOCALAPPDATA || join(input.home, "AppData", "Local"), "norn", "pi");
23
+ if (input.environment.NORN_PI_CACHE_DIR)
24
+ return resolve(input.environment.NORN_PI_CACHE_DIR);
25
+ if (input.platform === "darwin")
26
+ return join(input.home, "Library", "Caches", "norn", "pi");
27
+ if (input.platform === "win32")
28
+ return join(
29
+ input.environment.LOCALAPPDATA || join(input.home, "AppData", "Local"),
30
+ "norn",
31
+ "pi"
32
+ );
17
33
  const xdgCache = input.environment.XDG_CACHE_HOME;
18
- return join(xdgCache?.startsWith("/") ? xdgCache : join(input.home, ".cache"), "norn", "pi");
34
+ return join(
35
+ xdgCache?.startsWith("/") ? xdgCache : join(input.home, ".cache"),
36
+ "norn",
37
+ "pi"
38
+ );
19
39
  }
20
40
  async function materializePiAssets(input) {
21
41
  const digest = createHash("sha256").update(input.archive).digest("hex");
22
- const files = JSON.parse(gunzipSync(Buffer.from(input.archive, "base64")).toString("utf8"));
42
+ const files = JSON.parse(
43
+ gunzipSync(Buffer.from(input.archive, "base64")).toString("utf8")
44
+ );
23
45
  const expectedFiles = /* @__PURE__ */ new Map();
24
46
  for (const file of files) {
25
47
  if (!file.path || file.path.includes("\\") || file.path.includes(":") || file.path.split("/").some((part) => !part || part === "." || part === "..") || expectedFiles.has(file.path)) {
@@ -27,7 +49,11 @@ async function materializePiAssets(input) {
27
49
  }
28
50
  expectedFiles.set(file.path, Buffer.from(file.content, "base64"));
29
51
  }
30
- return new PiAssetCache({ cacheRoot: input.cacheRoot, digest, expectedFiles }).materialize();
52
+ return new PiAssetCache({
53
+ cacheRoot: input.cacheRoot,
54
+ digest,
55
+ expectedFiles
56
+ }).materialize();
31
57
  }
32
58
  var PiAssetCache = class {
33
59
  constructor(input) {
@@ -38,21 +64,32 @@ var PiAssetCache = class {
38
64
  root;
39
65
  async materialize() {
40
66
  await mkdir(this.input.cacheRoot, { recursive: true, mode: 448 });
41
- if (!(await lstat(this.input.cacheRoot)).isDirectory()) throw new Error("Pi asset cache root must be a real directory");
67
+ if (!(await lstat(this.input.cacheRoot)).isDirectory())
68
+ throw new Error("Pi asset cache root must be a real directory");
42
69
  if (await this.validateExistingCache()) return this.root;
43
70
  const staging = await mkdtemp(join(this.input.cacheRoot, ".extract-"));
44
71
  try {
45
72
  for (const [path, content] of this.input.expectedFiles) {
46
73
  const parts = path.split("/");
47
- await mkdir(join(staging, ...parts.slice(0, -1)), { recursive: true, mode: 448 });
48
- await writeFile(join(staging, ...parts), content, { flag: "wx", mode: 384 });
74
+ await mkdir(join(staging, ...parts.slice(0, -1)), {
75
+ recursive: true,
76
+ mode: 448
77
+ });
78
+ await writeFile(join(staging, ...parts), content, {
79
+ flag: "wx",
80
+ mode: 384
81
+ });
49
82
  }
50
83
  try {
51
84
  await rename(staging, this.root);
52
85
  } catch (error) {
53
- if (!isNodeError(error) || !["EEXIST", "ENOTEMPTY", "EPERM", "EACCES"].includes(error.code ?? "") || !await this.validateExistingCache()) throw error;
86
+ if (!isNodeError(error) || !["EEXIST", "ENOTEMPTY", "EPERM", "EACCES"].includes(
87
+ error.code ?? ""
88
+ ) || !await this.validateExistingCache())
89
+ throw error;
54
90
  }
55
- if (!await this.validateExistingCache()) throw new Error("Pi assets disappeared after extraction");
91
+ if (!await this.validateExistingCache())
92
+ throw new Error("Pi assets disappeared after extraction");
56
93
  return this.root;
57
94
  } finally {
58
95
  await rm(staging, { recursive: true, force: true });
@@ -60,32 +97,46 @@ var PiAssetCache = class {
60
97
  }
61
98
  async validateExistingCache() {
62
99
  try {
63
- if (!(await lstat(this.root)).isDirectory()) throw this.invalidCacheError();
100
+ if (!(await lstat(this.root)).isDirectory())
101
+ throw this.invalidCacheError();
64
102
  } catch (error) {
65
103
  if (isNodeError(error) && error.code === "ENOENT") return false;
66
104
  throw error;
67
105
  }
68
106
  const seen = /* @__PURE__ */ new Set();
69
107
  await this.validateDirectory({ directory: this.root, prefix: "", seen });
70
- if (seen.size !== this.input.expectedFiles.size) throw this.invalidCacheError();
108
+ if (seen.size !== this.input.expectedFiles.size)
109
+ throw this.invalidCacheError();
71
110
  return true;
72
111
  }
73
112
  async validateDirectory(input) {
74
- for (const entry of await readdir(input.directory, { withFileTypes: true })) {
113
+ for (const entry of await readdir(input.directory, {
114
+ withFileTypes: true
115
+ })) {
75
116
  const path = input.prefix ? `${input.prefix}/${entry.name}` : entry.name;
76
117
  const absolutePath = join(input.directory, entry.name);
77
118
  if (entry.isDirectory()) {
78
- if (![...this.input.expectedFiles.keys()].some((file) => file.startsWith(`${path}/`))) throw this.invalidCacheError();
79
- await this.validateDirectory({ directory: absolutePath, prefix: path, seen: input.seen });
119
+ if (![...this.input.expectedFiles.keys()].some(
120
+ (file) => file.startsWith(`${path}/`)
121
+ ))
122
+ throw this.invalidCacheError();
123
+ await this.validateDirectory({
124
+ directory: absolutePath,
125
+ prefix: path,
126
+ seen: input.seen
127
+ });
80
128
  } else {
81
129
  const expected = this.input.expectedFiles.get(path);
82
- if (!entry.isFile() || !expected || !(await readFile(absolutePath)).equals(expected)) throw this.invalidCacheError();
130
+ if (!entry.isFile() || !expected || !(await readFile(absolutePath)).equals(expected))
131
+ throw this.invalidCacheError();
83
132
  input.seen.add(path);
84
133
  }
85
134
  }
86
135
  }
87
136
  invalidCacheError() {
88
- return new Error(`Bundled Pi assets are incomplete or modified: ${this.root}. Remove this cache entry and retry.`);
137
+ return new Error(
138
+ `Bundled Pi assets are incomplete or modified: ${this.root}. Remove this cache entry and retry.`
139
+ );
89
140
  }
90
141
  };
91
142
  export {