@llblab/pi-actors 0.45.0 → 0.45.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.
@@ -7,6 +7,8 @@ import { dirname, extname, join, normalize, relative, resolve } from "node:path"
7
7
  import { spawnSync } from "node:child_process";
8
8
  import { fileURLToPath } from "node:url";
9
9
 
10
+ import { executableBlockBlankLines } from "./executable-block-style.mjs";
11
+
10
12
  const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
11
13
  const temp = mkdtempSync(join(tmpdir(), "pi-actors-release-index-"));
12
14
  const indexPath = join(temp, "index");
@@ -71,7 +73,6 @@ try {
71
73
  .filter(Boolean);
72
74
  const fileSet = new Set(files);
73
75
  console.log(`[release] exact temporary index: ${files.length} files`);
74
-
75
76
  const secretPatterns = [
76
77
  [/-----BEGIN(?: [A-Z0-9]+)? PRIVATE KEY-----[\s\S]{80,}?-----END(?: [A-Z0-9]+)? PRIVATE KEY-----/u, "private-key block"],
77
78
  [/\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/u, "AWS access key"],
@@ -92,7 +93,17 @@ try {
92
93
  }
93
94
  }
94
95
  console.log("[release] bounded secret and public-path hygiene checked");
95
-
96
+ const executableSources = files.filter((path) =>
97
+ /\.(?:[cm]?js|ts)$/u.test(path) && !path.startsWith("dist/"),
98
+ );
99
+ for (const path of executableSources) {
100
+ const blankLines = executableBlockBlankLines(stagedText(path) ?? "");
101
+ check(
102
+ blankLines.length === 0,
103
+ `blank lines inside executable blocks: ${path}:${blankLines.join(",")}`,
104
+ );
105
+ }
106
+ console.log("[release] executable blocks contain no blank lines");
96
107
  const removedPaths = [
97
108
  "lib/messages.ts",
98
109
  "lib/rooms.ts",
@@ -113,7 +124,6 @@ try {
113
124
  "lib/run-inspector-overlay.ts",
114
125
  ];
115
126
  for (const path of removedPaths) check(!fileSet.has(path), `removed surface returned: ${path}`);
116
-
117
127
  const currentSurface = /^(?:lib\/|scripts\/|recipes\/|fixtures\/|skills\/|docs\/|README\.md|AGENTS\.md)/u;
118
128
  const forbiddenPatterns = [
119
129
  [/room:</u, "room address"],
@@ -144,7 +154,6 @@ try {
144
154
  }
145
155
  }
146
156
  console.log("[release] removed-surface and legacy-fallback allowlists checked");
147
-
148
157
  const directTraceWrite = /(?:appendFileSync|writeFileSync|writeText(?:Atomic)?)\s*\(\s*[A-Za-z0-9_.]*?(?:trace|event)(?:Path|File)/iu;
149
158
  for (const path of files.filter((candidate) =>
150
159
  (candidate.startsWith("lib/") && candidate.endsWith(".ts")) ||
@@ -172,7 +181,6 @@ try {
172
181
  );
173
182
  }
174
183
  console.log("[release] canonical Trace and Control writer residue checked");
175
-
176
184
  // owner | class | lock | record bound | byte bound | recovery
177
185
  const appendOnlyOwners = new Set([
178
186
  "lib/runs-trace.ts", // canonical Run evidence | token lock | 2048 | 4 MiB | compact valid suffix
@@ -189,7 +197,6 @@ try {
189
197
  check(appendOnlyOwners.has(path), `unregistered shipped append-only writer: ${path}`);
190
198
  for (const path of appendOnlyOwners) check(fileSet.has(path), `stale append-only owner inventory: ${path}`);
191
199
  console.log(`[release] append-only owner inventory checked: ${appendOnlyOwners.size} source owners`);
192
-
193
200
  check(
194
201
  (stagedText("scripts/validate-recipe.mjs") ?? "").includes(
195
202
  "qaReport.diagnostics.length === 0 && qaReport.warnings.length === 0",
@@ -197,15 +204,13 @@ try {
197
204
  "Recipe QA warnings are not release-blocking",
198
205
  );
199
206
  console.log("[release] zero-warning Recipe QA gate checked");
200
-
201
- const maximumShippedLines = 29_767;
207
+ const maximumShippedLines = 32_000;
202
208
  const shippedPath = /^(?:lib\/|scripts\/|recipes\/|docs\/|skills\/)/u;
203
209
  const shippedLines = files
204
210
  .filter((path) => shippedPath.test(path))
205
211
  .reduce((total, path) => total + (((stagedText(path) ?? "").match(/\n/gu) ?? []).length + 1), 0);
206
212
  check(shippedLines <= maximumShippedLines, `shipped lines ${shippedLines} exceed release maximum ${maximumShippedLines}`);
207
213
  console.log(`[release] shipped lines ${shippedLines} <= release maximum ${maximumShippedLines}`);
208
-
209
214
  const sources = files.filter((path) => path === "index.ts" || (path.startsWith("lib/") && path.endsWith(".ts")));
210
215
  const sourceSet = new Set(sources);
211
216
  const graph = new Map();
@@ -240,7 +245,6 @@ try {
240
245
  }
241
246
  for (const path of sources) visit(path);
242
247
  console.log(`[release] strict Domain DAG: ${sources.length} sources, acyclic`);
243
-
244
248
  for (const path of ["README.md", "AGENTS.md", "BACKLOG.md", "CHANGELOG.md", "docs/README.md"]) {
245
249
  check(fileSet.has(path), `missing ABCd root/context file: ${path}`);
246
250
  }
@@ -263,7 +267,6 @@ try {
263
267
  check(docsIndex.includes(relative("docs", path).replaceAll("\\", "/")), `docs/README.md omits ${path}`);
264
268
  }
265
269
  console.log("[release] ABCd context roots, routing, and Markdown links checked");
266
-
267
270
  if (failures.length > 0) {
268
271
  for (const failure of failures) console.error(`[FAIL] ${failure}`);
269
272
  process.exitCode = 1;
@@ -187,7 +187,6 @@ export function validateRecipes(argv) {
187
187
  if (!targetArg || argv.includes("--help") || argv.includes("-h")) {
188
188
  return { help: true, ok: Boolean(targetArg), usage: validateRecipeUsage() };
189
189
  }
190
-
191
190
  const files = recipeFiles(expandPath(targetArg), all);
192
191
  const results = files.map((file) => validateFile(file, qa));
193
192
  const failed = results.filter((result) => !result.ok).length;
package/index.ts CHANGED
@@ -1,124 +1,29 @@
1
1
  /**
2
2
  * pi-actors — actor runtime and persistent local tool registry for pi.
3
3
  * Zones: composition root, pi agent, actor runtime
4
- *
5
- * Wraps command templates as callable pi tools, stores durable user tools as recipe files, and exposes actor orchestration across reloads and sessions.
4
+ * Owns extension composition and Pi event registration, not domain behavior.
6
5
  */
7
6
 
8
- import * as AutomaticReviewRuntime from "./lib/automatic-review-runtime.ts";
9
- import * as CommandTemplates from "./lib/command-templates.ts";
7
+ import * as ExtensionRuntime from "./lib/extension-runtime.ts";
10
8
  import * as InspectorCommand from "./lib/inspector-command.ts";
11
- import * as Paths from "./lib/paths.ts";
12
9
  import * as Pi from "./lib/pi.ts";
13
- import * as Prompts from "./lib/prompts.ts";
14
- import * as RecipesReferences from "./lib/recipes-references.ts";
15
- import * as RunUiRuntime from "./lib/run-ui-runtime.ts";
16
- import * as Runtime from "./lib/runtime.ts";
17
- import * as Temp from "./lib/temp.ts";
18
- import * as Tools from "./lib/tools.ts";
19
- import * as ToolsResponse from "./lib/tools-response.ts";
20
10
 
21
11
  export default function toolRegistryExtension(pi: Pi.ExtensionAPI) {
22
- let activeRunContext: Pi.ExtensionContext | undefined;
23
- const getRunOwnerId = Pi.getSessionId;
24
- const automaticReview = AutomaticReviewRuntime.createAutomaticReviewRuntime({
25
- getActiveContext: () => activeRunContext,
26
- getRunOwnerId,
27
- getThinkingLevel: () => pi.getThinkingLevel(),
28
- });
29
- const runUiRuntime = RunUiRuntime.createRunUiRuntime({
30
- getActiveContext: () => activeRunContext,
31
- getRunOwnerId,
32
- onRunEvent: automaticReview.schedule,
33
- pi,
34
- });
35
- const actorToolDefinitions = new Map<string, Tools.ActorToolDefinition>();
36
- const withCurrentThinkingContext = <T extends Tools.ActorToolDefinition>(
37
- definition: T,
38
- ): T => {
39
- if (typeof definition.execute !== "function") return definition;
40
- const execute = definition.execute as (...args: unknown[]) => unknown;
41
- return {
42
- ...definition,
43
- execute: async (...args: unknown[]) => {
44
- const nextArgs = [...args];
45
- const ctx = nextArgs[4];
46
- if (ctx && typeof ctx === "object") {
47
- nextArgs[4] = {
48
- ...(ctx as Record<string, unknown>),
49
- getThinkingLevel: () => pi.getThinkingLevel(),
50
- };
51
- }
52
- try {
53
- return ToolsResponse.spaceToolResult(await execute(...nextArgs));
54
- } catch (error) {
55
- throw ToolsResponse.spaceToolError(error);
56
- }
57
- },
58
- } as T;
59
- };
60
- const runtime = Runtime.createAutoToolsRuntime({
61
- configPath: Paths.EXTENSION_RUNTIME_PATHS.configPath,
62
- exec: CommandTemplates.execCommandTemplate,
63
- getActiveTools: () => pi.getActiveTools(),
64
- registerTool: (definition) => {
65
- const wrapped = withCurrentThinkingContext(definition);
66
- actorToolDefinitions.set(wrapped.name, wrapped);
67
- pi.registerTool(wrapped);
68
- },
69
- reservedToolNames: Tools.RESERVED_TOOL_NAMES,
70
- setActiveTools: (toolNames) => pi.setActiveTools(toolNames),
71
- });
72
- const recipeReload = Runtime.createRecipeToolReloadWatcher(runtime);
73
- pi.on("resources_discover", async () => {
74
- const skillPaths = Paths.getExistingExtensionSkillPaths(import.meta.url);
75
- if (skillPaths.length === 0) return;
76
- return { skillPaths };
77
- });
78
- pi.on("session_start", async (_event, ctx) => {
79
- // Clear the pre-overlay widget after hot reloads from older pi-actors builds.
80
- ctx.ui.setWidget("zz-pi-actors-comms", undefined);
81
- activeRunContext = ctx;
82
- runUiRuntime.close();
83
- automaticReview.close();
84
- recipeReload.close();
85
- await Temp.prepareExtensionTempDir(Paths.EXTENSION_RUNTIME_PATHS.tempDir);
86
- if (activeRunContext !== ctx) return;
87
- automaticReview.start(ctx);
88
- runtime.loadTools(ctx);
89
- runUiRuntime.start(ctx);
90
- recipeReload.watch(ctx);
91
- });
92
- pi.on("agent_end", async (_event, ctx) => {
93
- if (activeRunContext === ctx) automaticReview.schedule();
94
- });
95
- pi.on("session_shutdown", async (event, ctx) => {
96
- activeRunContext = undefined;
97
- automaticReview.close();
98
- recipeReload.close();
99
- runUiRuntime.shutdown(event.reason, ctx);
100
- });
101
- InspectorCommand.registerActorInspectorCommand(pi, getRunOwnerId);
102
- pi.on("before_agent_start", async (event) => {
103
- RecipesReferences.setActiveSkillRecipeSources(
12
+ const runtime = ExtensionRuntime.createActorExtensionRuntime(pi);
13
+ pi.on("resources_discover", async () =>
14
+ runtime.discoverResources(import.meta.url),
15
+ );
16
+ pi.on("session_start", async (_event, ctx) => runtime.onSessionStart(ctx));
17
+ pi.on("agent_end", async (_event, ctx) => runtime.onAgentEnd(ctx));
18
+ pi.on("session_shutdown", async (event, ctx) =>
19
+ runtime.onSessionShutdown(event.reason, ctx),
20
+ );
21
+ pi.on("before_agent_start", async (event) =>
22
+ runtime.beforeAgentStart(
23
+ event.systemPrompt,
104
24
  event.systemPromptOptions.skills ?? [],
105
- );
106
- return {
107
- systemPrompt: `${event.systemPrompt}\n\n${Prompts.ONBOARDING_SYSTEM_PROMPT}`,
108
- };
109
- });
110
- Pi.registerToolDefinitions(
111
- pi,
112
- Tools.createCoreActorToolDefinitions<Pi.ExtensionContext>({
113
- configPath: Paths.EXTENSION_RUNTIME_PATHS.configPath,
114
- getActiveTools: () => pi.getActiveTools(),
115
- getRuntimeTool: (name) =>
116
- Tools.resolveActiveRuntimeTool(name, runtime.getTools(), (activeName) =>
117
- actorToolDefinitions.get(activeName),
118
- ),
119
- handleRuntimeControl: automaticReview.handleControl,
120
- registryRuntime: runtime,
121
- setActiveTools: (toolNames) => pi.setActiveTools(toolNames),
122
- }).map(withCurrentThinkingContext),
25
+ ),
123
26
  );
27
+ InspectorCommand.registerActorInspectorCommand(pi, runtime.getRunOwnerId);
28
+ runtime.registerCoreTools();
124
29
  }
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Pi actor extension runtime coordinator.
3
+ * Zones: extension session lifecycle, host tool adaptation, runtime service composition
4
+ * Owns low-level Pi lifecycle effects and tool wrapping without owning event registration.
5
+ */
6
+
7
+ import * as AutomaticReviewRuntime from "./automatic-review-runtime.ts";
8
+ import * as CommandTemplates from "./command-templates.ts";
9
+ import * as Paths from "./paths.ts";
10
+ import * as Pi from "./pi.ts";
11
+ import * as Prompts from "./prompts.ts";
12
+ import * as RecipesReferences from "./recipes-references.ts";
13
+ import * as RunUiRuntime from "./run-ui-runtime.ts";
14
+ import * as Runtime from "./runtime.ts";
15
+ import * as Temp from "./temp.ts";
16
+ import * as Tools from "./tools.ts";
17
+ import * as ToolsResponse from "./tools-response.ts";
18
+
19
+ export interface ActorExtensionRuntime {
20
+ beforeAgentStart(
21
+ systemPrompt: string,
22
+ skills: RecipesReferences.ActiveSkillRecipeSource[],
23
+ ): { systemPrompt: string };
24
+ discoverResources(metaUrl: string): { skillPaths: string[] } | undefined;
25
+ getRunOwnerId(ctx: Pi.ExtensionContext): string;
26
+ onAgentEnd(ctx: Pi.ExtensionContext): void;
27
+ onSessionShutdown(reason: string, ctx: Pi.ExtensionContext): void;
28
+ onSessionStart(ctx: Pi.ExtensionContext): Promise<void>;
29
+ registerCoreTools(): void;
30
+ }
31
+
32
+ export function createActorExtensionRuntime(
33
+ pi: Pi.ExtensionAPI,
34
+ ): ActorExtensionRuntime {
35
+ let activeRunContext: Pi.ExtensionContext | undefined;
36
+ const getRunOwnerId = Pi.getSessionId;
37
+ const automaticReview = AutomaticReviewRuntime.createAutomaticReviewRuntime({
38
+ getActiveContext: () => activeRunContext,
39
+ getRunOwnerId,
40
+ getThinkingLevel: () => pi.getThinkingLevel(),
41
+ });
42
+ const runUiRuntime = RunUiRuntime.createRunUiRuntime({
43
+ getActiveContext: () => activeRunContext,
44
+ getRunOwnerId,
45
+ onRunEvent: automaticReview.schedule,
46
+ pi,
47
+ });
48
+ const actorToolDefinitions = new Map<string, Tools.ActorToolDefinition>();
49
+ const withCurrentThinkingContext = <T extends Tools.ActorToolDefinition>(
50
+ definition: T,
51
+ ): T => {
52
+ if (typeof definition.execute !== "function") return definition;
53
+ const execute = definition.execute as (...args: unknown[]) => unknown;
54
+ return {
55
+ ...definition,
56
+ execute: async (...args: unknown[]) => {
57
+ const nextArgs = [...args];
58
+ const ctx = nextArgs[4];
59
+ if (ctx && typeof ctx === "object") {
60
+ nextArgs[4] = {
61
+ ...(ctx as Record<string, unknown>),
62
+ getThinkingLevel: () => pi.getThinkingLevel(),
63
+ };
64
+ }
65
+ try {
66
+ return ToolsResponse.spaceToolResult(await execute(...nextArgs));
67
+ } catch (error) {
68
+ throw ToolsResponse.spaceToolError(error);
69
+ }
70
+ },
71
+ } as T;
72
+ };
73
+ const runtime = Runtime.createAutoToolsRuntime({
74
+ configPath: Paths.EXTENSION_RUNTIME_PATHS.configPath,
75
+ exec: CommandTemplates.execCommandTemplate,
76
+ getActiveTools: () => pi.getActiveTools(),
77
+ registerTool: (definition) => {
78
+ const wrapped = withCurrentThinkingContext(definition);
79
+ actorToolDefinitions.set(wrapped.name, wrapped);
80
+ pi.registerTool(wrapped);
81
+ },
82
+ reservedToolNames: Tools.RESERVED_TOOL_NAMES,
83
+ setActiveTools: (toolNames) => pi.setActiveTools(toolNames),
84
+ });
85
+ const recipeReload = Runtime.createRecipeToolReloadWatcher(runtime);
86
+ return {
87
+ beforeAgentStart(systemPrompt, skills) {
88
+ RecipesReferences.setActiveSkillRecipeSources(skills);
89
+ return {
90
+ systemPrompt: `${systemPrompt}\n\n${Prompts.ONBOARDING_SYSTEM_PROMPT}`,
91
+ };
92
+ },
93
+ discoverResources(metaUrl) {
94
+ const skillPaths = Paths.getExistingExtensionSkillPaths(metaUrl);
95
+ return skillPaths.length > 0 ? { skillPaths } : undefined;
96
+ },
97
+ getRunOwnerId,
98
+ onAgentEnd(ctx) {
99
+ if (activeRunContext === ctx) automaticReview.schedule();
100
+ },
101
+ onSessionShutdown(reason, ctx) {
102
+ activeRunContext = undefined;
103
+ automaticReview.close();
104
+ recipeReload.close();
105
+ runUiRuntime.shutdown(reason, ctx);
106
+ },
107
+ async onSessionStart(ctx) {
108
+ ctx.ui.setWidget("zz-pi-actors-comms", undefined);
109
+ activeRunContext = ctx;
110
+ runUiRuntime.close();
111
+ automaticReview.close();
112
+ recipeReload.close();
113
+ await Temp.prepareExtensionTempDir(Paths.EXTENSION_RUNTIME_PATHS.tempDir);
114
+ if (activeRunContext !== ctx) return;
115
+ automaticReview.start(ctx);
116
+ runtime.loadTools(ctx);
117
+ runUiRuntime.start(ctx);
118
+ recipeReload.watch(ctx);
119
+ },
120
+ registerCoreTools() {
121
+ Pi.registerToolDefinitions(
122
+ pi,
123
+ Tools.createCoreActorToolDefinitions<Pi.ExtensionContext>({
124
+ configPath: Paths.EXTENSION_RUNTIME_PATHS.configPath,
125
+ getActiveTools: () => pi.getActiveTools(),
126
+ getRuntimeTool: (name) =>
127
+ Tools.resolveActiveRuntimeTool(
128
+ name,
129
+ runtime.getTools(),
130
+ (activeName) => actorToolDefinitions.get(activeName),
131
+ ),
132
+ handleRuntimeControl: automaticReview.handleControl,
133
+ registryRuntime: runtime,
134
+ setActiveTools: (toolNames) => pi.setActiveTools(toolNames),
135
+ }).map(withCurrentThinkingContext),
136
+ );
137
+ },
138
+ };
139
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-actors",
3
- "version": "0.45.0",
3
+ "version": "0.45.1",
4
4
  "private": false,
5
5
  "description": "Local Actor Kernel for Pi",
6
6
  "keywords": [
@@ -65,7 +65,7 @@
65
65
  "sourceSkills": [
66
66
  "./skills"
67
67
  ],
68
- "image": "https://github.com/llblab/pi-actors/raw/main/banner.jpg"
68
+ "image": "https://raw.githubusercontent.com/llblab/pi-actors/main/banner.jpg"
69
69
  },
70
70
  "peerDependencies": {
71
71
  "@earendil-works/pi-coding-agent": "*",
@@ -74,7 +74,6 @@ function summarizeCommandDetail(commandDetail) {
74
74
 
75
75
  export async function runAsyncRunner(stateDir = process.argv[2]) {
76
76
  if (!stateDir) throw new Error("missing state dir");
77
-
78
77
  const runPath = join(stateDir, "run.json");
79
78
  const progressPath = join(stateDir, "progress.json");
80
79
  const resultPath = join(stateDir, "result.json");
@@ -83,11 +82,9 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
83
82
  const stderrPath = join(stateDir, "stderr.log");
84
83
  const executionPath = join(stateDir, "execution.json");
85
84
  const meta = JSON.parse(readFileSync(runPath, "utf8"));
86
-
87
85
  function event(kind, data = {}) {
88
86
  appendRunTraceEvent(stateDir, { kind, data });
89
87
  }
90
-
91
88
  function observation(kind, summary, data = {}, delivery = "log", level = "info") {
92
89
  appendRunTraceEvent(stateDir, {
93
90
  kind,
@@ -97,7 +94,6 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
97
94
  ...(level === "error" ? { level: "error" } : {}),
98
95
  });
99
96
  }
100
-
101
97
  function progress(phase, extra = {}) {
102
98
  writeJsonAtomic(progressPath, {
103
99
  ...(meta.model_policy ? { model_policy: meta.model_policy } : {}),
@@ -106,7 +102,6 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
106
102
  ...extra,
107
103
  });
108
104
  }
109
-
110
105
  let activeSubagents = 0;
111
106
  let completedSubagents = 0;
112
107
  let promptCounter = 0;
@@ -115,7 +110,6 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
115
110
  const evidenceRecords = [];
116
111
  const stageOccurrences = new Map();
117
112
  let reportEvidence;
118
-
119
113
  function writeExecutionManifest(status) {
120
114
  writeJsonAtomic(executionPath, {
121
115
  version: 1,
@@ -129,7 +123,6 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
129
123
  updated_at: new Date().toISOString(),
130
124
  });
131
125
  }
132
-
133
126
  function commandSessionFiles(sessionDir) {
134
127
  if (!sessionDir) return [];
135
128
  try {
@@ -141,7 +134,6 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
141
134
  return [];
142
135
  }
143
136
  }
144
-
145
137
  function commandEvidenceStartRecord({
146
138
  commandDetail,
147
139
  commandId,
@@ -181,7 +173,6 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
181
173
  : "not_required",
182
174
  };
183
175
  }
184
-
185
176
  function commandEvidenceRecord({
186
177
  captureDir,
187
178
  commandDetail,
@@ -273,7 +264,6 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
273
264
  : "rejected",
274
265
  };
275
266
  }
276
-
277
267
  function auditReviewReport(text) {
278
268
  const required = evidenceRecords
279
269
  .filter((record) =>
@@ -294,11 +284,9 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
294
284
  complete_allowed: missing.length === 0,
295
285
  };
296
286
  }
297
-
298
287
  function getCommandDoneDelivery(result) {
299
288
  return result.code !== 0 || activeSubagents > 0 ? "followup" : "log";
300
289
  }
301
-
302
290
  function progressRunning() {
303
291
  progress("running", {
304
292
  activeSubagents,
@@ -306,14 +294,12 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
306
294
  failures: subagentFailures,
307
295
  });
308
296
  }
309
-
310
297
  function promptFilePath() {
311
298
  promptCounter += 1;
312
299
  const dir = join(stateDir, "prompts");
313
300
  mkdirSync(dir, { recursive: true });
314
301
  return join(dir, `command-${String(promptCounter).padStart(3, "0")}.md`);
315
302
  }
316
-
317
303
  function readPromptText(promptFile) {
318
304
  if (!promptFile) return undefined;
319
305
  try {
@@ -322,7 +308,6 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
322
308
  return undefined;
323
309
  }
324
310
  }
325
-
326
311
  async function observedExec(command, args, options) {
327
312
  captureCounter += 1;
328
313
  const commandId = `command-${String(captureCounter).padStart(3, "0")}`;
@@ -485,7 +470,6 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
485
470
  progressRunning();
486
471
  return result;
487
472
  }
488
-
489
473
  try {
490
474
  event("run.runner.start", { pid: process.pid });
491
475
  progressRunning();
@@ -0,0 +1 @@
1
+ export function executableBlockBlankLines(source: string): number[];
@@ -0,0 +1,73 @@
1
+ /** Detect blank lines inside executable JavaScript and TypeScript blocks. */
2
+
3
+ export function executableBlockBlankLines(source) {
4
+ const lines = source.split(/\r?\n/u);
5
+ const blanks = [];
6
+ const stack = [];
7
+ let blockComment = false;
8
+ let currentExecutableDepth = 0;
9
+ let previousCode = "";
10
+ let quote;
11
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
12
+ const line = lines[lineIndex];
13
+ if (
14
+ /^\s*$/u.test(line) &&
15
+ currentExecutableDepth > 0 &&
16
+ quote !== "`"
17
+ )
18
+ blanks.push(lineIndex + 1);
19
+ let code = "";
20
+ let regularExpression = false;
21
+ for (let index = 0; index < line.length; index += 1) {
22
+ const char = line[index];
23
+ const next = line[index + 1];
24
+ if (blockComment) {
25
+ if (char === "*" && next === "/") {
26
+ blockComment = false;
27
+ index += 1;
28
+ }
29
+ continue;
30
+ }
31
+ if (quote) {
32
+ if (char === "\\") index += 1;
33
+ else if (char === quote) quote = undefined;
34
+ continue;
35
+ }
36
+ if (regularExpression) {
37
+ if (char === "\\") index += 1;
38
+ else if (char === "/") regularExpression = false;
39
+ continue;
40
+ }
41
+ if (char === "/" && next === "*") {
42
+ blockComment = true;
43
+ index += 1;
44
+ continue;
45
+ }
46
+ if (char === "/" && next === "/") break;
47
+ if (
48
+ char === "/" &&
49
+ /^(?:|.*(?:[=(,:;!&|?{}]|\b(?:return|case|throw)))\s*$/u.test(code)
50
+ ) {
51
+ regularExpression = true;
52
+ continue;
53
+ }
54
+ if (char === '"' || char === "'" || char === "`") {
55
+ quote = char;
56
+ continue;
57
+ }
58
+ if (char === "{") {
59
+ const prefix = code.trimEnd() || previousCode;
60
+ const executable =
61
+ /\)\s*$|=>\s*$|\b(?:else|try|finally|do)\s*$/u.test(prefix);
62
+ stack.push(executable);
63
+ if (executable) currentExecutableDepth += 1;
64
+ } else if (char === "}") {
65
+ if (stack.pop()) currentExecutableDepth -= 1;
66
+ }
67
+ code += char;
68
+ }
69
+ if (quote !== "`") quote = undefined;
70
+ if (code.trim()) previousCode = code.trim();
71
+ }
72
+ return blanks;
73
+ }