@tt-a1i/openpi 0.1.0 → 0.2.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.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +295 -389
  3. package/SETUP.md +24 -22
  4. package/THIRD_PARTY_NOTICES.md +3 -4
  5. package/assets/readme-hero-mobile.svg +2 -2
  6. package/assets/readme-hero.svg +10 -10
  7. package/extensions/ask-user/handoff.ts +5 -1
  8. package/extensions/ask-user/index.ts +44 -0
  9. package/extensions/background-terminals/index.ts +118 -29
  10. package/extensions/background-terminals/src/domain.ts +5 -1
  11. package/extensions/background-terminals/src/manager.ts +2 -1
  12. package/extensions/background-terminals/src/prompt.ts +35 -0
  13. package/extensions/background-terminals/src/result-delivery.ts +76 -3
  14. package/extensions/background-terminals/src/ui/tool-result.ts +52 -1
  15. package/extensions/capabilities/index.ts +198 -0
  16. package/extensions/context-pivot/index.ts +21 -0
  17. package/extensions/cron/index.ts +42 -15
  18. package/extensions/execution-convergence/active-evidence.ts +129 -0
  19. package/extensions/execution-convergence/index.ts +442 -0
  20. package/extensions/execution-convergence/workspace-provenance.ts +338 -0
  21. package/extensions/file-search/index.ts +8 -1
  22. package/extensions/file-search/src/binaries.ts +2 -1
  23. package/extensions/git-info/src/runtime.ts +1 -1
  24. package/extensions/goal/controller.ts +2 -1
  25. package/extensions/goal/index.ts +20 -1
  26. package/extensions/plan-mode/index.ts +12 -0
  27. package/extensions/setup/index.ts +241 -45
  28. package/extensions/setup/intercom-fs-helper.cjs +130 -0
  29. package/extensions/setup/intercom.ts +603 -0
  30. package/extensions/shared/child-session.ts +42 -5
  31. package/extensions/shared/setup-config.ts +27 -1
  32. package/extensions/shared/setup-episode-state.ts +7 -0
  33. package/extensions/shared/tool-surface.ts +435 -0
  34. package/extensions/subagents/index.ts +16 -1
  35. package/extensions/subagents/src/manager.ts +13 -11
  36. package/extensions/subagents/src/prompt.ts +1 -1
  37. package/extensions/tasks/index.ts +39 -12
  38. package/extensions/ui-customization/footer.ts +6 -1
  39. package/extensions/workflows/artifacts.ts +6 -1
  40. package/extensions/workflows/dashboard.ts +138 -27
  41. package/extensions/workflows/graph-projection.ts +240 -0
  42. package/extensions/workflows/handoff.ts +194 -0
  43. package/extensions/workflows/index.ts +258 -56
  44. package/extensions/workflows/invocation-ledger.ts +368 -0
  45. package/extensions/workflows/model.ts +57 -1
  46. package/extensions/workflows/operator.ts +131 -0
  47. package/extensions/workflows/prompt.ts +10 -38
  48. package/extensions/workflows/replay-safety.ts +9 -8
  49. package/extensions/workflows/runner.ts +10 -2
  50. package/extensions/workflows/sandbox.ts +5 -0
  51. package/package.json +15 -15
  52. package/skills/subagents/SKILL.md +6 -0
  53. package/skills/workflows/EXAMPLES.md +58 -0
  54. package/skills/workflows/REFERENCE.md +44 -0
  55. package/skills/workflows/SKILL.md +39 -0
@@ -1,4 +1,7 @@
1
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
1
+ import type {
2
+ ExtensionAPI,
3
+ ExtensionCommandContext,
4
+ } from "@earendil-works/pi-coding-agent";
2
5
  import { StringEnum } from "@earendil-works/pi-ai";
3
6
  import { Type } from "typebox";
4
7
  import {
@@ -6,8 +9,21 @@ import {
6
9
  type SubagentRoleModel,
7
10
  type SubagentRoleModels,
8
11
  } from "../shared/subagent-roles.ts";
12
+ import { sanitizeTerminalText } from "../shared/terminal-text.ts";
13
+ import {
14
+ OPENPI_SETUP_EPISODE_CHANNEL,
15
+ type OpenPiSetupEpisodeState,
16
+ } from "../shared/setup-episode-state.ts";
17
+ import { patchOwnedTools } from "../shared/tool-surface.ts";
18
+ import {
19
+ formatPiIntercomStatus,
20
+ inspectPiIntercom,
21
+ installPiIntercom,
22
+ type PiIntercomStatus,
23
+ } from "./intercom.ts";
9
24
  import {
10
25
  applyFooterConfig,
26
+ CAPABILITY_DISCOVERY_MODES,
11
27
  DETAIL_DISPLAYS,
12
28
  FOOTER_ITEMS,
13
29
  FOOTER_LAYOUT_ITEMS,
@@ -23,6 +39,7 @@ import {
23
39
  REASONING_LEVELS,
24
40
  SETUP_CONFIG_CHANGED_CHANNEL,
25
41
  type FooterLayoutItem,
42
+ type CapabilityDiscoveryMode,
26
43
  type FooterPreset,
27
44
  type FooterStyle,
28
45
  type MyPiSetupConfig,
@@ -92,16 +109,16 @@ export function buildInteractiveSetupPrompt(options: {
92
109
  }) {
93
110
  const configurationState = options.savedConfigExists
94
111
  ? [
95
- "This package has already been configured. Explain the current settings in the user's language, then ask whether they want to keep them or change Next-action suggestions, Workflow limits, UI/Footer, result detail display, Post-edit, Agent role models, or review everything.",
112
+ "This package has already been configured. Explain the current settings in the user's language, then ask whether they want to keep them or change Capability discovery, Next-action suggestions, Workflow limits, UI/Footer, result detail display, Post-edit, Agent role models, or review everything.",
96
113
  "If the user keeps the current settings, do not call configure_my_pi_setup. If they choose a category, ask only the follow-up needed for that category.",
97
114
  ]
98
115
  : [
99
116
  "This is the first setup. Explain the available choices and their impact in the user's language, then collect the initial preferences.",
100
- "Prefer one ask_user call with up to three independent questions covering Next-action suggestions, Workflow limits, and UI/Footer/result display. Explain that Post-edit defaults off; keep it off unless the user opts in, then ask only for the command. Explain that built-in Agent roles used by subagent_spawn and workflow agent_type inherit the parent model unless the user assigns an available model to a role.",
117
+ "Prefer one ask_user call with up to three independent questions covering Capability discovery plus Workflow limits, Next-action suggestions, and UI/Footer/result display. Explain that Post-edit defaults off; keep it off unless the user opts in, then ask only for the command. Explain that built-in Agent roles used by subagent_spawn and workflow agent_type inherit the parent model unless the user assigns an available model to a role.",
101
118
  ];
102
119
 
103
120
  return [
104
- "Guide me through configuring the installed my-pi-setup package interactively.",
121
+ "Guide me through configuring the installed OpenPI package interactively.",
105
122
  "",
106
123
  "Current configuration:",
107
124
  options.currentConfiguration,
@@ -112,6 +129,7 @@ export function buildInteractiveSetupPrompt(options: {
112
129
  ...configurationState,
113
130
  "",
114
131
  "Before asking, briefly explain what can be configured and the practical impact:",
132
+ "- Capability discovery: explicit is the safe default and keeps OpenPI model tools absent until the user asks for a capability. adaptive is opt-in and keeps only the small openpi_load_tools gateway visible, allowing the model to load Subagents, Workflows, background terminals, structured search, or Session tracking when it judges them useful. Loaded groups remain session-stable, and normal permission, concurrency, and workflow limits still apply.",
115
133
  "- Next-action suggestions: disabled, or model-generated after a fully settled main-agent run. A suggestion appears as dim inline text on the first row of an empty editor; reserved cells at the row end keep CJK IME preedit from overwriting it. Right accepts it without submitting, and any other editor input dismisses it. Enabling requires an available provider/model and reasoning level and adds one small model call per settled run.",
116
134
  "- Workflow fan-out: concurrency controls simultaneous agents and resource pressure; max agent calls controls the total capacity of one workflow. Valid ranges are 1-64 and 1-1024.",
117
135
  "- UI: the large header costs vertical space; the custom footer is a declarative dashboard. Presets: powerline (default one-line ANSI256 blocks), powerline-mono (one-line high-contrast gray powerline), and compact (one-line plain text). Style can also be set independently: plain, powerline, powerline-mono. Custom lines are a 2D layout of cwd/model/thinking/context/cache/cost/throughput/git/pr plus at most one flex per line for left/right alignment. Nerd Font only affects powerline separator glyphs; text stays readable without it. Changes apply immediately in the active TUI session.",
@@ -119,8 +137,11 @@ export function buildInteractiveSetupPrompt(options: {
119
137
  "- Post-edit command: one optional shell command (maximum 500 characters) run in the background after a turn with successful Write/Edit operations (e.g. `npm run format`). Off by default, interactive TUI sessions only, failures surface as a notification. This is a single command, not an event-hook system.",
120
138
  "- Result detail display: Subagent results, Bash operations, and Write/Edit operations can each default to full (always expanded) or compact (Claude Code-style folded preview with a hidden-line count). Compact output can still be temporarily expanded with the configured app.tools.expand key (Ctrl+O by default). Bash and Write/Edit default to compact. Recommend compact for users who do not usually inspect implementation details.",
121
139
  "- Agent role models: built-in explorer, implementer, reviewer, and advisor roles are shared by subagent_spawn and workflow agent_type, and inherit the parent model by default. Assign only an available registry model to an individual role when needed; clearing that role returns it to inheritance. Custom agent-type files still override a built-in role's complete definition.",
140
+ "- Intercom: optional cross-session messaging is installed only after a native setup confirmation. It stays parent-only; Direct/Workflow children and Replay cannot use it. The status above is informational for this model-guided step—do not install packages or edit its config yourself.",
122
141
  "",
123
142
  "Natural-language configuration examples the user might ask for:",
143
+ '- "let the model discover OpenPI capabilities when useful" → capability_discovery=adaptive',
144
+ '- "only use OpenPI capabilities when I ask" → capability_discovery=explicit',
124
145
  '- "switch footer to powerline" → ui_footer_preset=powerline',
125
146
  '- "use mono powerline" → ui_footer_preset=powerline-mono',
126
147
  '- "compact footer" → ui_footer_preset=compact',
@@ -134,13 +155,168 @@ export function buildInteractiveSetupPrompt(options: {
134
155
  ];
135
156
  }
136
157
 
137
- export default function myPiSetup(pi: ExtensionAPI) {
158
+ const safeSetupNotice = (value: unknown, maximum = 500) =>
159
+ sanitizeTerminalText(value instanceof Error ? value.message : String(value))
160
+ .replace(/\s+/gu, " ")
161
+ .trim()
162
+ .slice(0, maximum);
163
+
164
+ export function buildSetupSuccessText(
165
+ currentConfiguration: string,
166
+ normalizationNote = "",
167
+ ) {
168
+ return [
169
+ `Updated OpenPI setup. ${currentConfiguration}${normalizationNote}`,
170
+ "This setup episode is complete; configure_my_pi_setup is now hidden. Do not call it again. Do not edit configuration files directly. If the user requests another configuration change, tell them to run /openpi-setup <request> to start a new setup episode.",
171
+ ].join(" ");
172
+ }
173
+
174
+ export function shouldOfferPiIntercom(options: {
175
+ readonly request: string;
176
+ readonly status: PiIntercomStatus;
177
+ readonly mode: ExtensionCommandContext["mode"];
178
+ readonly idle: boolean;
179
+ }) {
180
+ return (
181
+ !options.request &&
182
+ !options.status.active &&
183
+ !options.status.installed &&
184
+ !options.status.diagnostic &&
185
+ options.mode === "tui" &&
186
+ options.idle
187
+ );
188
+ }
189
+
190
+ async function maybeOfferPiIntercom(
191
+ ctx: ExtensionCommandContext,
192
+ status: PiIntercomStatus,
193
+ request: string,
194
+ ) {
195
+ if (
196
+ !shouldOfferPiIntercom({
197
+ request,
198
+ status,
199
+ mode: ctx.mode,
200
+ idle: ctx.isIdle(),
201
+ })
202
+ ) {
203
+ return status;
204
+ }
205
+
206
+ const accepted = await ctx.ui.confirm(
207
+ status.configured
208
+ ? "Repair optional pi-intercom integration?"
209
+ : "Install optional pi-intercom integration?",
210
+ [
211
+ "pi-intercom enables cross-session messaging through a local IPC broker.",
212
+ "Like every Pi package, it runs with full system access.",
213
+ "OpenPI will install npm:pi-intercom globally. A new private config gets safe defaults; an existing preference file is never rewritten and must already define both fields:",
214
+ "• confirmSend: true",
215
+ '• inboundTrigger: "replies"',
216
+ "It remains parent-only and activates after /reload.",
217
+ ].join("\n"),
218
+ );
219
+ if (!accepted) return status;
220
+
221
+ ctx.ui.setWorkingMessage("Installing optional pi-intercom integration...");
222
+ try {
223
+ await installPiIntercom({
224
+ cwd: ctx.cwd,
225
+ onProgress: (event) =>
226
+ ctx.ui.setWorkingMessage(
227
+ safeSetupNotice(
228
+ event.message ?? "Installing optional pi-intercom integration...",
229
+ 200,
230
+ ),
231
+ ),
232
+ });
233
+ const installed = inspectPiIntercom({
234
+ cwd: ctx.cwd,
235
+ active: false,
236
+ });
237
+ const next = { ...installed, reloadRequired: true };
238
+ ctx.ui.notify(
239
+ "pi-intercom installed with existing preferences preserved or a new safe config created. Run /reload after setup to activate it.",
240
+ "info",
241
+ );
242
+ return next;
243
+ } catch (error) {
244
+ ctx.ui.notify(
245
+ `pi-intercom was not enabled: ${safeSetupNotice(error)}`,
246
+ "error",
247
+ );
248
+ return inspectPiIntercom({ cwd: ctx.cwd, active: false });
249
+ } finally {
250
+ ctx.ui.setWorkingMessage();
251
+ }
252
+ }
253
+
254
+ export const CONFIGURE_MY_PI_SETUP_TOOL_NAME = "configure_my_pi_setup";
255
+
256
+ type SetupEpisode = "idle" | "armed" | "active";
257
+
258
+ function showConfigureTool(pi: ExtensionAPI) {
259
+ patchOwnedTools(pi, "setup", {
260
+ enable: [CONFIGURE_MY_PI_SETUP_TOOL_NAME],
261
+ });
262
+ }
263
+
264
+ function hideConfigureTool(pi: ExtensionAPI) {
265
+ patchOwnedTools(pi, "setup", {
266
+ disable: [CONFIGURE_MY_PI_SETUP_TOOL_NAME],
267
+ });
268
+ }
269
+
270
+ export default function openPiSetup(pi: ExtensionAPI) {
271
+ let episode: SetupEpisode = "idle";
272
+ const publishEpisode = () =>
273
+ pi.events.emit(OPENPI_SETUP_EPISODE_CHANNEL, {
274
+ active: episode !== "idle",
275
+ } satisfies OpenPiSetupEpisodeState);
276
+
277
+ const endEpisode = () => {
278
+ episode = "idle";
279
+ hideConfigureTool(pi);
280
+ publishEpisode();
281
+ };
282
+
283
+ pi.on("session_start", () => {
284
+ endEpisode();
285
+ });
286
+
287
+ pi.on("agent_start", () => {
288
+ if (episode === "armed") {
289
+ episode = "active";
290
+ publishEpisode();
291
+ }
292
+ });
293
+
294
+ pi.on("tool_execution_end", (event) => {
295
+ if (
296
+ episode === "active" &&
297
+ event.toolName === CONFIGURE_MY_PI_SETUP_TOOL_NAME &&
298
+ !event.isError
299
+ ) {
300
+ endEpisode();
301
+ }
302
+ });
303
+
304
+ pi.on("agent_settled", () => {
305
+ if (episode === "active") endEpisode();
306
+ });
307
+
138
308
  pi.registerTool({
139
309
  name: "configure_my_pi_setup",
140
- label: "Configure My Pi Setup",
310
+ label: "Configure OpenPI",
141
311
  description:
142
- "Apply a user-requested configuration change for this Pi setup. Configures next-action suggestions, workflow fan-out, UI/Footer (presets, style, multi-line layout), result detail display, optional Post-edit, and built-in Agent-role model assignments shared by subagent_spawn and workflow agent_type. Role models must be available in the Pi registry; null clears a role back to parent-model inheritance. Footer examples: powerline preset, powerline-mono, compact, or custom ui_footer_lines with flex. Preserve current values for settings the user did not ask to change. Changes apply immediately to an active TUI footer.",
312
+ "Apply a user-requested configuration change for this Pi setup. Configures capability discovery (explicit or opt-in adaptive), next-action suggestions, workflow fan-out, UI/Footer (presets, style, multi-line layout), result detail display, optional Post-edit, and built-in Agent-role model assignments shared by subagent_spawn and workflow agent_type. Role models must be available in the Pi registry; null clears a role back to parent-model inheritance. Footer examples: powerline preset, powerline-mono, compact, or custom ui_footer_lines with flex. Preserve current values for settings the user did not ask to change. Changes apply immediately to the capability gateway and active TUI footer.",
143
313
  parameters: Type.Object({
314
+ capability_discovery: Type.Optional(
315
+ StringEnum(CAPABILITY_DISCOVERY_MODES, {
316
+ description:
317
+ "Capability adoption policy. explicit keeps OpenPI tools absent until the user asks for a capability; adaptive keeps only the small openpi_load_tools gateway visible so the model may load a useful group on its own. Adaptive can start expensive work such as Subagents or Workflows, so it is opt-in. Omit to preserve the current value.",
318
+ }),
319
+ ),
144
320
  suggestions_enabled: Type.Optional(
145
321
  Type.Boolean({
146
322
  description:
@@ -309,6 +485,12 @@ export default function myPiSetup(pi: ExtensionAPI) {
309
485
  );
310
486
 
311
487
  const config: MyPiSetupConfig = {
488
+ capabilities: {
489
+ discovery:
490
+ (params.capability_discovery as
491
+ | CapabilityDiscoveryMode
492
+ | undefined) ?? current.capabilities.discovery,
493
+ },
312
494
  suggestions: {
313
495
  enabled: suggestionsEnabled,
314
496
  ...(model ? { model } : {}),
@@ -360,49 +542,63 @@ export default function myPiSetup(pi: ExtensionAPI) {
360
542
  : "";
361
543
  if (ctx.hasUI) ctx.ui.notify(`${text}${note}`, "info");
362
544
  return {
363
- content: [
364
- { type: "text", text: `Updated my Pi setup. ${text}${note}` },
365
- ],
545
+ content: [{ type: "text", text: buildSetupSuccessText(text, note) }],
366
546
  details: config,
367
547
  };
368
548
  },
369
549
  });
370
550
 
551
+ const setupHandler = async (args: string, ctx: ExtensionCommandContext) => {
552
+ const request = args.trim();
553
+ let intercomStatus = inspectPiIntercom({
554
+ cwd: ctx.cwd,
555
+ active: pi.getAllTools().some(({ name }) => name === "intercom"),
556
+ });
557
+ intercomStatus = await maybeOfferPiIntercom(ctx, intercomStatus, request);
558
+
559
+ const currentConfiguration = formatSetupConfig(loadSetupConfig(), [
560
+ formatPiIntercomStatus(intercomStatus),
561
+ ]);
562
+ const savedConfigExists = hasSavedSetupConfig();
563
+ const currentModel = ctx.model
564
+ ? `${ctx.model.provider}/${ctx.model.id}`
565
+ : "unavailable";
566
+ const currentThinking = pi.getThinkingLevel();
567
+
568
+ const prompt = request
569
+ ? [
570
+ "Configure the installed OpenPI package according to this request:",
571
+ request,
572
+ "",
573
+ "Current configuration:",
574
+ currentConfiguration,
575
+ "",
576
+ "Capability discovery is explicit by default; adaptive is an opt-in that keeps only openpi_load_tools visible so the model may load useful groups. Footer tips: presets are powerline, powerline-mono, compact; style is plain/powerline/powerline-mono; custom layouts use ui_footer_lines (2D enum arrays with optional flex). Do not use ui_footer_items together with ui_footer_lines. Built-in Agent role models (explorer, implementer, reviewer, advisor) are shared by subagent_spawn and workflow agent_type; they inherit the parent unless assigned an available registry model, and clearing an assignment restores inheritance. Custom agent-type files still override built-in role definitions. Nerd Font only affects powerline separator glyphs. Changes apply immediately in the active TUI session. Intercom installation is handled only by the native setup confirmation; do not install packages or edit its config yourself.",
577
+ "",
578
+ "Use configure_my_pi_setup to apply only the requested OpenPI-owned changes and preserve everything else. Interpret model names from the available Pi registry. Do not edit configuration files directly.",
579
+ ]
580
+ : buildInteractiveSetupPrompt({
581
+ currentConfiguration,
582
+ currentModel,
583
+ currentThinking,
584
+ savedConfigExists,
585
+ });
586
+
587
+ episode = "armed";
588
+ showConfigureTool(pi);
589
+ publishEpisode();
590
+ pi.sendUserMessage(
591
+ prompt.join("\n"),
592
+ ctx.isIdle() ? undefined : { deliverAs: "followUp" },
593
+ );
594
+ };
595
+
596
+ pi.registerCommand("openpi-setup", {
597
+ description: "View or change OpenPI configuration in natural language",
598
+ handler: setupHandler,
599
+ });
371
600
  pi.registerCommand("my-pi-setup", {
372
- description:
373
- "View or change this package's configuration in natural language",
374
- handler: async (args, ctx) => {
375
- const request = args.trim();
376
- const currentConfiguration = formatSetupConfig(loadSetupConfig());
377
- const savedConfigExists = hasSavedSetupConfig();
378
- const currentModel = ctx.model
379
- ? `${ctx.model.provider}/${ctx.model.id}`
380
- : "unavailable";
381
- const currentThinking = pi.getThinkingLevel();
382
-
383
- const prompt = request
384
- ? [
385
- "Configure the installed my-pi-setup package according to this request:",
386
- request,
387
- "",
388
- "Current configuration:",
389
- currentConfiguration,
390
- "",
391
- "Footer tips: presets are powerline, powerline-mono, compact; style is plain/powerline/powerline-mono; custom layouts use ui_footer_lines (2D enum arrays with optional flex). Do not use ui_footer_items together with ui_footer_lines. Built-in Agent role models (explorer, implementer, reviewer, advisor) are shared by subagent_spawn and workflow agent_type; they inherit the parent unless assigned an available registry model, and clearing an assignment restores inheritance. Custom agent-type files still override built-in role definitions. Nerd Font only affects powerline separator glyphs. Changes apply immediately in the active TUI session.",
392
- "",
393
- "Use configure_my_pi_setup to apply only the requested changes and preserve everything else. Interpret model names from the available Pi registry. Do not edit configuration files directly.",
394
- ]
395
- : buildInteractiveSetupPrompt({
396
- currentConfiguration,
397
- currentModel,
398
- currentThinking,
399
- savedConfigExists,
400
- });
401
-
402
- pi.sendUserMessage(
403
- prompt.join("\n"),
404
- ctx.isIdle() ? undefined : { deliverAs: "followUp" },
405
- );
406
- },
601
+ description: "Legacy alias — use /openpi-setup",
602
+ handler: setupHandler,
407
603
  });
408
604
  }
@@ -0,0 +1,130 @@
1
+ const { randomUUID } = require("node:crypto");
2
+ const {
3
+ constants,
4
+ closeSync,
5
+ fstatSync,
6
+ openSync,
7
+ readFileSync,
8
+ renameSync,
9
+ statSync,
10
+ unlinkSync,
11
+ writeFileSync,
12
+ } = require("node:fs");
13
+
14
+ const [operation, expectedDev, expectedIno, name, encoded] =
15
+ process.argv.slice(2);
16
+ const allowedNames = new Set([
17
+ "config.json",
18
+ "config.json.openpi-install.lock",
19
+ ]);
20
+ const fail = (code, message) => {
21
+ process.stderr.write(`OPENPI:${code}:${message}\n`);
22
+ process.exit(1);
23
+ };
24
+ const identityMatches = () => {
25
+ const current = statSync(".", { bigint: true });
26
+ return (
27
+ current.isDirectory() &&
28
+ String(current.dev) === expectedDev &&
29
+ String(current.ino) === expectedIno
30
+ );
31
+ };
32
+
33
+ if (!allowedNames.has(name)) fail("INPUT", "unsupported file name");
34
+ if (!identityMatches()) fail("IDENTITY", "directory identity mismatch");
35
+
36
+ const payload = Buffer.from(encoded ?? "", "base64");
37
+ if (payload.length > 8_192) fail("INPUT", "payload too large");
38
+
39
+ if (operation === "create") {
40
+ let fd;
41
+ try {
42
+ fd = openSync(
43
+ name,
44
+ constants.O_WRONLY |
45
+ constants.O_CREAT |
46
+ constants.O_EXCL |
47
+ (constants.O_NOFOLLOW ?? 0),
48
+ 0o600,
49
+ );
50
+ } catch (error) {
51
+ if (error && error.code === "EEXIST") fail("EEXIST", "file exists");
52
+ fail("IO", error instanceof Error ? error.message : String(error));
53
+ }
54
+ try {
55
+ const metadata = fstatSync(fd);
56
+ if (!metadata.isFile()) fail("TYPE", "created path is not a regular file");
57
+ writeFileSync(fd, payload);
58
+ } finally {
59
+ closeSync(fd);
60
+ }
61
+ if (!identityMatches()) fail("IDENTITY", "directory identity changed");
62
+ process.exit(0);
63
+ }
64
+
65
+ if (operation === "remove-owned") {
66
+ // Atomically move the current lock to a unique claim before inspecting it.
67
+ // A replacement lock can then appear at `name`, but cleanup never unlinks
68
+ // that pathname: it removes only inode-pinned, randomly named claims.
69
+ const claim = `.${name}.release.${process.pid}.${randomUUID()}`;
70
+ const markerClaim = `${claim}.marker`;
71
+ const releaseToken = Buffer.from(`release:${process.pid}:${randomUUID()}\n`);
72
+ try {
73
+ renameSync(name, claim);
74
+ } catch (error) {
75
+ fail("IO", error instanceof Error ? error.message : String(error));
76
+ }
77
+
78
+ let markerCreated = false;
79
+ let markerFd;
80
+ try {
81
+ markerFd = openSync(
82
+ name,
83
+ constants.O_WRONLY |
84
+ constants.O_CREAT |
85
+ constants.O_EXCL |
86
+ (constants.O_NOFOLLOW ?? 0),
87
+ 0o600,
88
+ );
89
+ writeFileSync(markerFd, releaseToken);
90
+ markerCreated = true;
91
+ } catch (error) {
92
+ if (!error || error.code !== "EEXIST") {
93
+ fail("IO", error instanceof Error ? error.message : String(error));
94
+ }
95
+ } finally {
96
+ if (markerFd !== undefined) closeSync(markerFd);
97
+ }
98
+
99
+ let claimFd;
100
+ try {
101
+ claimFd = openSync(claim, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
102
+ if (!fstatSync(claimFd).isFile())
103
+ fail("TYPE", "claim is not a regular file");
104
+ if (!readFileSync(claimFd).equals(payload))
105
+ fail("OWNER", "owner token mismatch");
106
+ } finally {
107
+ if (claimFd !== undefined) closeSync(claimFd);
108
+ }
109
+ unlinkSync(claim);
110
+
111
+ if (markerCreated) {
112
+ renameSync(name, markerClaim);
113
+ let releaseFd;
114
+ try {
115
+ releaseFd = openSync(
116
+ markerClaim,
117
+ constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0),
118
+ );
119
+ if (!readFileSync(releaseFd).equals(releaseToken))
120
+ fail("OWNER", "release marker ownership mismatch");
121
+ } finally {
122
+ if (releaseFd !== undefined) closeSync(releaseFd);
123
+ }
124
+ unlinkSync(markerClaim);
125
+ }
126
+ if (!identityMatches()) fail("IDENTITY", "directory identity changed");
127
+ process.exit(0);
128
+ }
129
+
130
+ fail("INPUT", "unsupported operation");