@agimon-ai/po-team 0.0.0-alpha.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/LICENSE +25 -0
- package/README.md +156 -0
- package/dist/agentDiscovery-Whwzc4dP.cjs +1637 -0
- package/dist/agentDiscovery-Whwzc4dP.cjs.map +1 -0
- package/dist/agentDiscovery-ZQlFkKvB.mjs +1529 -0
- package/dist/agentDiscovery-ZQlFkKvB.mjs.map +1 -0
- package/dist/apiContracts.cjs +162 -0
- package/dist/apiContracts.cjs.map +1 -0
- package/dist/apiContracts.d.mts +53 -0
- package/dist/apiContracts.d.mts.map +1 -0
- package/dist/apiContracts.mjs +157 -0
- package/dist/apiContracts.mjs.map +1 -0
- package/dist/atomicJson-D0346bt5.cjs +178 -0
- package/dist/atomicJson-D0346bt5.cjs.map +1 -0
- package/dist/atomicJson-D2t0CDh9.mjs +137 -0
- package/dist/atomicJson-D2t0CDh9.mjs.map +1 -0
- package/dist/capabilityCeiling-C-Y7H8oC.cjs +191 -0
- package/dist/capabilityCeiling-C-Y7H8oC.cjs.map +1 -0
- package/dist/capabilityCeiling-CF7kAS18.mjs +156 -0
- package/dist/capabilityCeiling-CF7kAS18.mjs.map +1 -0
- package/dist/capabilityCeiling.cjs +10 -0
- package/dist/capabilityCeiling.d.mts +46 -0
- package/dist/capabilityCeiling.d.mts.map +1 -0
- package/dist/capabilityCeiling.mjs +3 -0
- package/dist/delegation.cjs +49 -0
- package/dist/delegation.cjs.map +1 -0
- package/dist/delegation.d.mts +269 -0
- package/dist/delegation.d.mts.map +1 -0
- package/dist/delegation.mjs +34 -0
- package/dist/delegation.mjs.map +1 -0
- package/dist/delegationApi.cjs +92 -0
- package/dist/delegationApi.d.mts +2 -0
- package/dist/delegationApi.mjs +2 -0
- package/dist/env.cjs +62 -0
- package/dist/env.d.mts +2 -0
- package/dist/env.mjs +2 -0
- package/dist/environment-CaCW3SgT.cjs +320 -0
- package/dist/environment-CaCW3SgT.cjs.map +1 -0
- package/dist/environment-DrqhrxG_.mjs +75 -0
- package/dist/environment-DrqhrxG_.mjs.map +1 -0
- package/dist/environment-ma5oQ7uM.d.mts +63 -0
- package/dist/environment-ma5oQ7uM.d.mts.map +1 -0
- package/dist/extensions/server.cjs +6940 -0
- package/dist/extensions/server.cjs.map +1 -0
- package/dist/extensions/server.d.mts +5 -0
- package/dist/extensions/server.d.mts.map +1 -0
- package/dist/extensions/server.mjs +6932 -0
- package/dist/extensions/server.mjs.map +1 -0
- package/dist/extensions/web.mjs +2308 -0
- package/dist/extensions/web.mjs.map +1 -0
- package/dist/externalProcessIpc-C-15lJrB.mjs +584 -0
- package/dist/externalProcessIpc-C-15lJrB.mjs.map +1 -0
- package/dist/externalProcessIpc-ojE0pvw0.cjs +690 -0
- package/dist/externalProcessIpc-ojE0pvw0.cjs.map +1 -0
- package/dist/fablePlan.cjs +17 -0
- package/dist/fablePlan.d.mts +2 -0
- package/dist/fablePlan.mjs +2 -0
- package/dist/fablePlanApi-BsHhgVFV.mjs +118 -0
- package/dist/fablePlanApi-BsHhgVFV.mjs.map +1 -0
- package/dist/fablePlanApi-CZ3uUcMi.d.mts +97 -0
- package/dist/fablePlanApi-CZ3uUcMi.d.mts.map +1 -0
- package/dist/fablePlanApi-wBu9bRXU.cjs +207 -0
- package/dist/fablePlanApi-wBu9bRXU.cjs.map +1 -0
- package/dist/fablePlanApi.cjs +17 -0
- package/dist/fablePlanApi.d.mts +2 -0
- package/dist/fablePlanApi.mjs +2 -0
- package/dist/index.cjs +49 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.mts +160 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +45 -0
- package/dist/index.mjs.map +1 -0
- package/dist/runs/background/cliRunnerEntry.cjs +539 -0
- package/dist/runs/background/cliRunnerEntry.cjs.map +1 -0
- package/dist/runs/background/cliRunnerEntry.d.mts +1 -0
- package/dist/runs/background/cliRunnerEntry.mjs +537 -0
- package/dist/runs/background/cliRunnerEntry.mjs.map +1 -0
- package/dist/runtimeSubagentPolicy.cjs +5 -0
- package/dist/runtimeSubagentPolicy.d.mts +2 -0
- package/dist/runtimeSubagentPolicy.mjs +2 -0
- package/dist/runtimeSubagentTool.cjs +11 -0
- package/dist/runtimeSubagentTool.d.mts +2 -0
- package/dist/runtimeSubagentTool.mjs +2 -0
- package/dist/subagentPolicy-ALJam4GN.d.mts +33 -0
- package/dist/subagentPolicy-ALJam4GN.d.mts.map +1 -0
- package/dist/subagentPolicy-DQRyKDH-.cjs +39 -0
- package/dist/subagentPolicy-DQRyKDH-.cjs.map +1 -0
- package/dist/subagentPolicy-DxjsKeyj.mjs +22 -0
- package/dist/subagentPolicy-DxjsKeyj.mjs.map +1 -0
- package/dist/subagentPolicy.cjs +5 -0
- package/dist/subagentPolicy.d.mts +2 -0
- package/dist/subagentPolicy.mjs +2 -0
- package/dist/subagentTool-BTssGLw8.d.mts +103 -0
- package/dist/subagentTool-BTssGLw8.d.mts.map +1 -0
- package/dist/subagentTool-Ca2zQXKw.cjs +227 -0
- package/dist/subagentTool-Ca2zQXKw.cjs.map +1 -0
- package/dist/subagentTool-WOYrjxgT.mjs +186 -0
- package/dist/subagentTool-WOYrjxgT.mjs.map +1 -0
- package/dist/subagentTool.cjs +10 -0
- package/dist/subagentTool.d.mts +2 -0
- package/dist/subagentTool.mjs +2 -0
- package/dist/teamSnapshot-DgaANhBs.mjs +24 -0
- package/dist/teamSnapshot-DgaANhBs.mjs.map +1 -0
- package/dist/teamSnapshot-oKuGFdYc.cjs +35 -0
- package/dist/teamSnapshot-oKuGFdYc.cjs.map +1 -0
- package/dist/teamSnapshot.cjs +4 -0
- package/dist/teamSnapshot.d.mts +59 -0
- package/dist/teamSnapshot.d.mts.map +1 -0
- package/dist/teamSnapshot.mjs +2 -0
- package/package.json +201 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { defineApiContract, jsonApiResponses } from "@agimon-ai/po-core/apiContracts";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
//#region src/schemas/apiContracts.ts
|
|
4
|
+
const S = Type.String();
|
|
5
|
+
const N = Type.Number();
|
|
6
|
+
const O = Type.Optional;
|
|
7
|
+
const literals = (values) => Type.Union(values.map((value) => Type.Literal(value)));
|
|
8
|
+
const TeamCatalogSchema = Type.Object({
|
|
9
|
+
cwd: S,
|
|
10
|
+
models: Type.Array(S),
|
|
11
|
+
warning: O(S),
|
|
12
|
+
agents: Type.Array(Type.Object({
|
|
13
|
+
name: S,
|
|
14
|
+
source: literals([
|
|
15
|
+
"project",
|
|
16
|
+
"user",
|
|
17
|
+
"plugin"
|
|
18
|
+
]),
|
|
19
|
+
packageName: O(S),
|
|
20
|
+
description: S,
|
|
21
|
+
model: O(S),
|
|
22
|
+
fallbackModels: Type.Array(S),
|
|
23
|
+
tools: Type.Array(S),
|
|
24
|
+
skills: Type.Array(S),
|
|
25
|
+
extensions: Type.Array(S),
|
|
26
|
+
defaultContext: literals(["fresh", "fork"]),
|
|
27
|
+
filePath: S
|
|
28
|
+
}))
|
|
29
|
+
});
|
|
30
|
+
const TeamRunsSchema = Type.Object({ runs: Type.Array(Type.Object({
|
|
31
|
+
runId: S,
|
|
32
|
+
agent: S,
|
|
33
|
+
state: literals([
|
|
34
|
+
"queued",
|
|
35
|
+
"running",
|
|
36
|
+
"done",
|
|
37
|
+
"failed",
|
|
38
|
+
"stopped"
|
|
39
|
+
]),
|
|
40
|
+
rawState: S,
|
|
41
|
+
task: S,
|
|
42
|
+
taskRef: O(S),
|
|
43
|
+
model: O(S),
|
|
44
|
+
cwd: S,
|
|
45
|
+
sessionFile: O(S),
|
|
46
|
+
startedAt: N,
|
|
47
|
+
endedAt: O(N),
|
|
48
|
+
lastUpdate: N,
|
|
49
|
+
currentTool: O(S),
|
|
50
|
+
toolCount: O(N),
|
|
51
|
+
tokens: O(N),
|
|
52
|
+
cost: O(N),
|
|
53
|
+
summary: O(S),
|
|
54
|
+
error: O(S),
|
|
55
|
+
tail: Type.Array(S)
|
|
56
|
+
})) });
|
|
57
|
+
const apiContracts = defineApiContract({
|
|
58
|
+
version: 1,
|
|
59
|
+
dynamic: [],
|
|
60
|
+
http: [
|
|
61
|
+
{
|
|
62
|
+
id: "team.catalog",
|
|
63
|
+
scope: "session",
|
|
64
|
+
basePath: "team",
|
|
65
|
+
path: "/catalog",
|
|
66
|
+
method: "GET",
|
|
67
|
+
authentication: "owner",
|
|
68
|
+
description: "Discover the active session agent catalog.",
|
|
69
|
+
responses: jsonApiResponses(TeamCatalogSchema)
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: "team.run",
|
|
73
|
+
scope: "session",
|
|
74
|
+
basePath: "team",
|
|
75
|
+
path: "/run",
|
|
76
|
+
method: "POST",
|
|
77
|
+
authentication: "owner",
|
|
78
|
+
description: "Launch an agent in the selected headless session.",
|
|
79
|
+
body: {
|
|
80
|
+
required: true,
|
|
81
|
+
contentType: "application/json",
|
|
82
|
+
schema: Type.Object({
|
|
83
|
+
agent: Type.String({ minLength: 1 }),
|
|
84
|
+
task: S,
|
|
85
|
+
fork: Type.Boolean(),
|
|
86
|
+
model: O(Type.String({ minLength: 1 }))
|
|
87
|
+
})
|
|
88
|
+
},
|
|
89
|
+
responses: jsonApiResponses(Type.Object({ runId: S }), 201)
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: "team.steer",
|
|
93
|
+
scope: "session",
|
|
94
|
+
basePath: "team",
|
|
95
|
+
path: "/steer",
|
|
96
|
+
method: "POST",
|
|
97
|
+
authentication: "owner",
|
|
98
|
+
description: "Send guidance straight to one running agent, without a parent prompt.",
|
|
99
|
+
body: {
|
|
100
|
+
required: true,
|
|
101
|
+
contentType: "application/json",
|
|
102
|
+
schema: Type.Object({
|
|
103
|
+
runId: Type.String({ minLength: 1 }),
|
|
104
|
+
message: Type.String({ minLength: 1 })
|
|
105
|
+
})
|
|
106
|
+
},
|
|
107
|
+
responses: jsonApiResponses(Type.Object({
|
|
108
|
+
requestId: S,
|
|
109
|
+
index: N,
|
|
110
|
+
state: literals([
|
|
111
|
+
"delivered",
|
|
112
|
+
"failed",
|
|
113
|
+
"pending"
|
|
114
|
+
]),
|
|
115
|
+
message: S
|
|
116
|
+
}))
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
id: "team.stop",
|
|
120
|
+
scope: "session",
|
|
121
|
+
basePath: "team",
|
|
122
|
+
path: "/stop",
|
|
123
|
+
method: "POST",
|
|
124
|
+
authentication: "owner",
|
|
125
|
+
description: "Ask the runtime to stop one running agent, without a parent prompt.",
|
|
126
|
+
body: {
|
|
127
|
+
required: true,
|
|
128
|
+
contentType: "application/json",
|
|
129
|
+
schema: Type.Object({ runId: Type.String({ minLength: 1 }) })
|
|
130
|
+
},
|
|
131
|
+
responses: jsonApiResponses(Type.Object({ requestId: S }))
|
|
132
|
+
}
|
|
133
|
+
],
|
|
134
|
+
sockets: ["global", "workspace"].flatMap((scope) => [{
|
|
135
|
+
id: "team.runs",
|
|
136
|
+
scope,
|
|
137
|
+
service: "po.hub.v1",
|
|
138
|
+
member: "subagent_runs",
|
|
139
|
+
kind: "channel",
|
|
140
|
+
direction: "server-to-client",
|
|
141
|
+
description: "Subagent run channel payload.",
|
|
142
|
+
input: TeamRunsSchema
|
|
143
|
+
}, {
|
|
144
|
+
id: "team.catalog.channel",
|
|
145
|
+
scope,
|
|
146
|
+
service: "po.hub.v1",
|
|
147
|
+
member: "subagent_catalog",
|
|
148
|
+
kind: "channel",
|
|
149
|
+
direction: "server-to-client",
|
|
150
|
+
description: "Subagent catalog channel payload.",
|
|
151
|
+
input: TeamCatalogSchema
|
|
152
|
+
}])
|
|
153
|
+
});
|
|
154
|
+
//#endregion
|
|
155
|
+
export { apiContracts, apiContracts as default };
|
|
156
|
+
|
|
157
|
+
//# sourceMappingURL=apiContracts.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apiContracts.mjs","names":[],"sources":["../src/schemas/apiContracts.ts"],"sourcesContent":["import { defineApiContract, jsonApiResponses } from '@agimon-ai/po-core/apiContracts';\nimport { Type } from 'typebox';\n\nconst S = Type.String();\nconst N = Type.Number();\nconst O = Type.Optional;\nconst literals = <T extends string>(values: T[]) => Type.Union(values.map((value) => Type.Literal(value)));\nexport const TeamCatalogSchema = Type.Object({\n cwd: S,\n models: Type.Array(S),\n warning: O(S),\n agents: Type.Array(\n Type.Object({\n name: S,\n source: literals(['project', 'user', 'plugin']),\n packageName: O(S),\n description: S,\n model: O(S),\n fallbackModels: Type.Array(S),\n tools: Type.Array(S),\n skills: Type.Array(S),\n extensions: Type.Array(S),\n defaultContext: literals(['fresh', 'fork']),\n filePath: S,\n }),\n ),\n});\nexport const TeamRunsSchema = Type.Object({\n runs: Type.Array(\n Type.Object({\n runId: S,\n agent: S,\n state: literals(['queued', 'running', 'done', 'failed', 'stopped']),\n rawState: S,\n task: S,\n taskRef: O(S),\n model: O(S),\n cwd: S,\n sessionFile: O(S),\n startedAt: N,\n endedAt: O(N),\n lastUpdate: N,\n currentTool: O(S),\n toolCount: O(N),\n tokens: O(N),\n cost: O(N),\n summary: O(S),\n error: O(S),\n tail: Type.Array(S),\n }),\n ),\n});\nexport const apiContracts = defineApiContract({\n version: 1,\n dynamic: [],\n http: [\n {\n id: 'team.catalog',\n scope: 'session',\n basePath: 'team',\n path: '/catalog',\n method: 'GET',\n authentication: 'owner',\n description: 'Discover the active session agent catalog.',\n responses: jsonApiResponses(TeamCatalogSchema),\n },\n {\n id: 'team.run',\n scope: 'session',\n basePath: 'team',\n path: '/run',\n method: 'POST',\n authentication: 'owner',\n description: 'Launch an agent in the selected headless session.',\n body: {\n required: true,\n contentType: 'application/json',\n schema: Type.Object({\n agent: Type.String({ minLength: 1 }),\n task: S,\n fork: Type.Boolean(),\n model: O(Type.String({ minLength: 1 })),\n }),\n },\n responses: jsonApiResponses(Type.Object({ runId: S }), 201),\n },\n {\n id: 'team.steer',\n scope: 'session',\n basePath: 'team',\n path: '/steer',\n method: 'POST',\n authentication: 'owner',\n description: 'Send guidance straight to one running agent, without a parent prompt.',\n body: {\n required: true,\n contentType: 'application/json',\n schema: Type.Object({ runId: Type.String({ minLength: 1 }), message: Type.String({ minLength: 1 }) }),\n },\n responses: jsonApiResponses(\n Type.Object({ requestId: S, index: N, state: literals(['delivered', 'failed', 'pending']), message: S }),\n ),\n },\n {\n id: 'team.stop',\n scope: 'session',\n basePath: 'team',\n path: '/stop',\n method: 'POST',\n authentication: 'owner',\n description: 'Ask the runtime to stop one running agent, without a parent prompt.',\n body: {\n required: true,\n contentType: 'application/json',\n schema: Type.Object({ runId: Type.String({ minLength: 1 }) }),\n },\n responses: jsonApiResponses(Type.Object({ requestId: S })),\n },\n ],\n sockets: (['global', 'workspace'] as const).flatMap((scope) => [\n {\n id: 'team.runs',\n scope,\n service: 'po.hub.v1',\n member: 'subagent_runs',\n kind: 'channel',\n direction: 'server-to-client',\n description: 'Subagent run channel payload.',\n input: TeamRunsSchema,\n },\n {\n id: 'team.catalog.channel',\n scope,\n service: 'po.hub.v1',\n member: 'subagent_catalog',\n kind: 'channel',\n direction: 'server-to-client',\n description: 'Subagent catalog channel payload.',\n input: TeamCatalogSchema,\n },\n ]),\n});\nexport default apiContracts;\n"],"mappings":"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"}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
|
+
key = keys[i];
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
12
|
+
get: ((k) => from[k]).bind(null, key),
|
|
13
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
19
|
+
value: mod,
|
|
20
|
+
enumerable: true
|
|
21
|
+
}) : target, mod));
|
|
22
|
+
//#endregion
|
|
23
|
+
let node_fs = require("node:fs");
|
|
24
|
+
node_fs = __toESM(node_fs, 1);
|
|
25
|
+
let node_path = require("node:path");
|
|
26
|
+
node_path = __toESM(node_path, 1);
|
|
27
|
+
//#region src/services/fileSystemRetry/index.ts
|
|
28
|
+
/**
|
|
29
|
+
* Retry helpers for transient filesystem failures.
|
|
30
|
+
*
|
|
31
|
+
* Windows holds brief directory and rename locks (antivirus, indexers, another
|
|
32
|
+
* process reading the file), surfacing as EACCES, EBUSY or EPERM. Those clear on
|
|
33
|
+
* their own, so the operation is retried on a backoff instead of failing a run.
|
|
34
|
+
*
|
|
35
|
+
* DESIGN PATTERNS:
|
|
36
|
+
* - Two variants with the same policy: a synchronous one for the atomic writers
|
|
37
|
+
* that must not yield mid-write, and an async one for everything else
|
|
38
|
+
*
|
|
39
|
+
* AVOID:
|
|
40
|
+
* - Reaching for the synchronous variant on a hot path; it blocks the event
|
|
41
|
+
* loop, and every millisecond of backoff is a millisecond the parent cannot
|
|
42
|
+
* service a child. Prefer `runFileSystemOperationWithRetryAsync`
|
|
43
|
+
* - Retrying ENOENT or ENOTDIR; those are real errors, not contention
|
|
44
|
+
*/
|
|
45
|
+
const WAIT_BUFFER = typeof SharedArrayBuffer !== "undefined" ? new SharedArrayBuffer(4) : void 0;
|
|
46
|
+
const WAIT_VIEW = WAIT_BUFFER ? new Int32Array(WAIT_BUFFER) : void 0;
|
|
47
|
+
const RETRYABLE_FILE_SYSTEM_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
48
|
+
"EACCES",
|
|
49
|
+
"EBUSY",
|
|
50
|
+
"EPERM"
|
|
51
|
+
]);
|
|
52
|
+
const DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS = [
|
|
53
|
+
10,
|
|
54
|
+
25,
|
|
55
|
+
50,
|
|
56
|
+
100,
|
|
57
|
+
200,
|
|
58
|
+
500,
|
|
59
|
+
1e3,
|
|
60
|
+
2e3,
|
|
61
|
+
4e3
|
|
62
|
+
];
|
|
63
|
+
/**
|
|
64
|
+
* Block the current thread for `delayMs`.
|
|
65
|
+
*
|
|
66
|
+
* Only for synchronous callers that cannot yield. `Atomics.wait` parks the
|
|
67
|
+
* thread rather than burning CPU; the loop below is a portable fallback for
|
|
68
|
+
* runtimes without SharedArrayBuffer and does spin, which is why async callers
|
|
69
|
+
* must use `delayForFileSystemRetry` instead.
|
|
70
|
+
*/
|
|
71
|
+
function waitForFileSystemRetry(delayMs) {
|
|
72
|
+
if (delayMs <= 0) return;
|
|
73
|
+
if (WAIT_VIEW) try {
|
|
74
|
+
Atomics.wait(WAIT_VIEW, 0, 0, delayMs);
|
|
75
|
+
return;
|
|
76
|
+
} catch {}
|
|
77
|
+
const end = Date.now() + delayMs;
|
|
78
|
+
while (Date.now() < end);
|
|
79
|
+
}
|
|
80
|
+
function isRetryableFileSystemError(error) {
|
|
81
|
+
const code = error?.code;
|
|
82
|
+
return typeof code === "string" && RETRYABLE_FILE_SYSTEM_ERROR_CODES.has(code);
|
|
83
|
+
}
|
|
84
|
+
function runFileSystemOperationWithRetry(operation, options = {}) {
|
|
85
|
+
const retryDelaysMs = options.retryDelaysMs ?? DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS;
|
|
86
|
+
const wait = options.wait ?? waitForFileSystemRetry;
|
|
87
|
+
for (let attempt = 0;; attempt++) try {
|
|
88
|
+
return operation();
|
|
89
|
+
} catch (error) {
|
|
90
|
+
const delayMs = retryDelaysMs[attempt];
|
|
91
|
+
if (delayMs === void 0 || !isRetryableFileSystemError(error)) throw error;
|
|
92
|
+
wait(delayMs);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region src/services/atomicJson/index.ts
|
|
97
|
+
/**
|
|
98
|
+
* Atomic JSON writes.
|
|
99
|
+
*
|
|
100
|
+
* DESIGN PATTERNS:
|
|
101
|
+
* - Write to a uniquely named temp file in the destination directory, then
|
|
102
|
+
* rename onto the target. Rename is atomic within a filesystem, so a reader
|
|
103
|
+
* sees either the previous file or the complete new one, never a partial write
|
|
104
|
+
* - The temp name carries pid, timestamp and a random suffix so two processes
|
|
105
|
+
* writing the same target never collide on the temp file
|
|
106
|
+
*
|
|
107
|
+
* WHAT THIS DOES NOT DO:
|
|
108
|
+
* This prevents torn reads. It does NOT prevent lost updates: two writers doing
|
|
109
|
+
* read-modify-write still clobber each other, because the rename only publishes
|
|
110
|
+
* whatever the caller already computed. Anything needing mutual exclusion has to
|
|
111
|
+
* take a lease or claim, not rely on atomic writes.
|
|
112
|
+
*
|
|
113
|
+
* AVOID:
|
|
114
|
+
* - Treating this as a lock
|
|
115
|
+
* - Using the plain writer for anything sensitive; prefer writePrivateAtomicJson,
|
|
116
|
+
* which creates the file 0600 so a shared temp directory does not leak prompts
|
|
117
|
+
*/
|
|
118
|
+
const PRIVATE_FILE_MODE = 384;
|
|
119
|
+
const JSON_INDENT = 2;
|
|
120
|
+
function createAtomicJsonWriter(options = {}) {
|
|
121
|
+
const fsImpl = options.fs ?? node_fs;
|
|
122
|
+
const now = options.now ?? Date.now;
|
|
123
|
+
const pid = options.pid ?? process.pid;
|
|
124
|
+
const random = options.random ?? Math.random;
|
|
125
|
+
const mode = options.mode;
|
|
126
|
+
const retryRenameErrors = options.retryRenameErrors ?? process.platform === "win32";
|
|
127
|
+
const retryDirectoryErrors = options.retryDirectoryErrors ?? retryRenameErrors;
|
|
128
|
+
const retryDelaysMs = options.retryDelaysMs ?? DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS;
|
|
129
|
+
const renameRetryDelaysMs = retryRenameErrors ? retryDelaysMs : [];
|
|
130
|
+
const directoryRetryDelaysMs = retryDirectoryErrors ? retryDelaysMs : [];
|
|
131
|
+
const wait = options.wait ?? waitForFileSystemRetry;
|
|
132
|
+
return (filePath, payload) => {
|
|
133
|
+
runFileSystemOperationWithRetry(() => {
|
|
134
|
+
fsImpl.mkdirSync(node_path.dirname(filePath), { recursive: true });
|
|
135
|
+
}, {
|
|
136
|
+
retryDelaysMs: directoryRetryDelaysMs,
|
|
137
|
+
wait
|
|
138
|
+
});
|
|
139
|
+
const tempPath = node_path.join(node_path.dirname(filePath), `.${node_path.basename(filePath)}.${pid}.${now()}.${random().toString(36).slice(2)}.tmp`);
|
|
140
|
+
try {
|
|
141
|
+
fsImpl.writeFileSync(tempPath, JSON.stringify(payload, null, JSON_INDENT), mode === void 0 ? "utf-8" : {
|
|
142
|
+
encoding: "utf-8",
|
|
143
|
+
mode
|
|
144
|
+
});
|
|
145
|
+
runFileSystemOperationWithRetry(() => {
|
|
146
|
+
fsImpl.renameSync(tempPath, filePath);
|
|
147
|
+
}, {
|
|
148
|
+
retryDelaysMs: renameRetryDelaysMs,
|
|
149
|
+
wait
|
|
150
|
+
});
|
|
151
|
+
} finally {
|
|
152
|
+
fsImpl.rmSync(tempPath, { force: true });
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
const writeAtomicJson = createAtomicJsonWriter();
|
|
157
|
+
const writePrivateAtomicJson = createAtomicJsonWriter({ mode: PRIVATE_FILE_MODE });
|
|
158
|
+
//#endregion
|
|
159
|
+
Object.defineProperty(exports, "__toESM", {
|
|
160
|
+
enumerable: true,
|
|
161
|
+
get: function() {
|
|
162
|
+
return __toESM;
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
Object.defineProperty(exports, "writeAtomicJson", {
|
|
166
|
+
enumerable: true,
|
|
167
|
+
get: function() {
|
|
168
|
+
return writeAtomicJson;
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
Object.defineProperty(exports, "writePrivateAtomicJson", {
|
|
172
|
+
enumerable: true,
|
|
173
|
+
get: function() {
|
|
174
|
+
return writePrivateAtomicJson;
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
//# sourceMappingURL=atomicJson-D0346bt5.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"atomicJson-D0346bt5.cjs","names":["fs","path"],"sources":["../src/services/fileSystemRetry/index.ts","../src/services/atomicJson/index.ts"],"sourcesContent":["/**\n * Retry helpers for transient filesystem failures.\n *\n * Windows holds brief directory and rename locks (antivirus, indexers, another\n * process reading the file), surfacing as EACCES, EBUSY or EPERM. Those clear on\n * their own, so the operation is retried on a backoff instead of failing a run.\n *\n * DESIGN PATTERNS:\n * - Two variants with the same policy: a synchronous one for the atomic writers\n * that must not yield mid-write, and an async one for everything else\n *\n * AVOID:\n * - Reaching for the synchronous variant on a hot path; it blocks the event\n * loop, and every millisecond of backoff is a millisecond the parent cannot\n * service a child. Prefer `runFileSystemOperationWithRetryAsync`\n * - Retrying ENOENT or ENOTDIR; those are real errors, not contention\n */\n\nconst WAIT_BUFFER = typeof SharedArrayBuffer !== 'undefined' ? new SharedArrayBuffer(4) : undefined;\nconst WAIT_VIEW = WAIT_BUFFER ? new Int32Array(WAIT_BUFFER) : undefined;\nconst RETRYABLE_FILE_SYSTEM_ERROR_CODES = new Set(['EACCES', 'EBUSY', 'EPERM']);\n\nexport const DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS = [10, 25, 50, 100, 200, 500, 1000, 2000, 4000] as const;\n\nexport type FileSystemRetryOptions = {\n retryDelaysMs?: readonly number[];\n wait?: (delayMs: number) => void;\n};\n\nexport type AsyncFileSystemRetryOptions = {\n retryDelaysMs?: readonly number[];\n wait?: (delayMs: number) => Promise<void>;\n};\n\n/**\n * Block the current thread for `delayMs`.\n *\n * Only for synchronous callers that cannot yield. `Atomics.wait` parks the\n * thread rather than burning CPU; the loop below is a portable fallback for\n * runtimes without SharedArrayBuffer and does spin, which is why async callers\n * must use `delayForFileSystemRetry` instead.\n */\nexport function waitForFileSystemRetry(delayMs: number): void {\n if (delayMs <= 0) return;\n if (WAIT_VIEW) {\n try {\n Atomics.wait(WAIT_VIEW, 0, 0, delayMs);\n return;\n } catch {\n // Fall through to the portable busy wait below.\n }\n }\n const end = Date.now() + delayMs;\n while (Date.now() < end) {\n // Portable fallback for runtimes where Atomics.wait is unavailable.\n }\n}\n\n/** Yield for `delayMs` without blocking the event loop. */\nexport function delayForFileSystemRetry(delayMs: number): Promise<void> {\n if (delayMs <= 0) return Promise.resolve();\n return new Promise((resolve) => {\n setTimeout(resolve, delayMs);\n });\n}\n\nexport function isRetryableFileSystemError(error: unknown): boolean {\n const code = (error as NodeJS.ErrnoException | undefined)?.code;\n return typeof code === 'string' && RETRYABLE_FILE_SYSTEM_ERROR_CODES.has(code);\n}\n\nexport function runFileSystemOperationWithRetry<T>(operation: () => T, options: FileSystemRetryOptions = {}): T {\n const retryDelaysMs = options.retryDelaysMs ?? DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS;\n const wait = options.wait ?? waitForFileSystemRetry;\n for (let attempt = 0; ; attempt++) {\n try {\n return operation();\n } catch (error) {\n const delayMs = retryDelaysMs[attempt];\n if (delayMs === undefined || !isRetryableFileSystemError(error)) throw error;\n wait(delayMs);\n }\n }\n}\n\nexport async function runFileSystemOperationWithRetryAsync<T>(\n operation: () => T | Promise<T>,\n options: AsyncFileSystemRetryOptions = {},\n): Promise<T> {\n const retryDelaysMs = options.retryDelaysMs ?? DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS;\n const wait = options.wait ?? delayForFileSystemRetry;\n for (let attempt = 0; ; attempt++) {\n try {\n return await operation();\n } catch (error) {\n const delayMs = retryDelaysMs[attempt];\n if (delayMs === undefined || !isRetryableFileSystemError(error)) throw error;\n await wait(delayMs);\n }\n }\n}\n","/**\n * Atomic JSON writes.\n *\n * DESIGN PATTERNS:\n * - Write to a uniquely named temp file in the destination directory, then\n * rename onto the target. Rename is atomic within a filesystem, so a reader\n * sees either the previous file or the complete new one, never a partial write\n * - The temp name carries pid, timestamp and a random suffix so two processes\n * writing the same target never collide on the temp file\n *\n * WHAT THIS DOES NOT DO:\n * This prevents torn reads. It does NOT prevent lost updates: two writers doing\n * read-modify-write still clobber each other, because the rename only publishes\n * whatever the caller already computed. Anything needing mutual exclusion has to\n * take a lease or claim, not rely on atomic writes.\n *\n * AVOID:\n * - Treating this as a lock\n * - Using the plain writer for anything sensitive; prefer writePrivateAtomicJson,\n * which creates the file 0600 so a shared temp directory does not leak prompts\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport {\n DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS,\n runFileSystemOperationWithRetry,\n runFileSystemOperationWithRetryAsync,\n waitForFileSystemRetry,\n} from '../fileSystemRetry';\n\nconst PRIVATE_FILE_MODE = 0o600;\nconst JSON_INDENT = 2;\n\ntype AtomicJsonFs = Pick<typeof fs, 'mkdirSync' | 'writeFileSync' | 'renameSync' | 'rmSync'>;\n\ntype AtomicJsonWriterOptions = {\n fs?: AtomicJsonFs;\n now?: () => number;\n pid?: number;\n random?: () => number;\n mode?: number;\n retryRenameErrors?: boolean;\n retryDirectoryErrors?: boolean;\n retryDelaysMs?: readonly number[];\n wait?: (delayMs: number) => void;\n};\n\nexport function createAtomicJsonWriter(\n options: AtomicJsonWriterOptions = {},\n): (filePath: string, payload: object) => void {\n const fsImpl = options.fs ?? fs;\n const now = options.now ?? Date.now;\n const pid = options.pid ?? process.pid;\n const random = options.random ?? Math.random;\n const mode = options.mode;\n // Retries exist for Windows directory and rename locks; other platforms fail fast.\n const retryRenameErrors = options.retryRenameErrors ?? process.platform === 'win32';\n const retryDirectoryErrors = options.retryDirectoryErrors ?? retryRenameErrors;\n const retryDelaysMs = options.retryDelaysMs ?? DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS;\n const renameRetryDelaysMs = retryRenameErrors ? retryDelaysMs : [];\n const directoryRetryDelaysMs = retryDirectoryErrors ? retryDelaysMs : [];\n const wait = options.wait ?? waitForFileSystemRetry;\n\n return (filePath: string, payload: object): void => {\n runFileSystemOperationWithRetry(\n () => {\n fsImpl.mkdirSync(path.dirname(filePath), { recursive: true });\n },\n { retryDelaysMs: directoryRetryDelaysMs, wait },\n );\n const tempPath = path.join(\n path.dirname(filePath),\n `.${path.basename(filePath)}.${pid}.${now()}.${random().toString(36).slice(2)}.tmp`,\n );\n try {\n fsImpl.writeFileSync(\n tempPath,\n JSON.stringify(payload, null, JSON_INDENT),\n mode === undefined ? 'utf-8' : { encoding: 'utf-8', mode },\n );\n runFileSystemOperationWithRetry(\n () => {\n fsImpl.renameSync(tempPath, filePath);\n },\n { retryDelaysMs: renameRetryDelaysMs, wait },\n );\n } finally {\n // Best effort: on the happy path the rename already consumed the temp file.\n fsImpl.rmSync(tempPath, { force: true });\n }\n };\n}\n\nexport const writeAtomicJson = createAtomicJsonWriter();\nexport const writePrivateAtomicJson = createAtomicJsonWriter({ mode: PRIVATE_FILE_MODE });\n\n/** Promise-based atomic writer for startup, polling, and other non-critical paths. */\nexport async function writeAtomicJsonAsync(filePath: string, payload: object, mode?: number): Promise<void> {\n const retryDelaysMs = process.platform === 'win32' ? DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS : [];\n await runFileSystemOperationWithRetryAsync(() => fs.promises.mkdir(path.dirname(filePath), { recursive: true }), {\n retryDelaysMs,\n });\n const tempPath = path.join(\n path.dirname(filePath),\n `.${path.basename(filePath)}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`,\n );\n try {\n await fs.promises.writeFile(\n tempPath,\n JSON.stringify(payload, null, JSON_INDENT),\n mode === undefined ? 'utf-8' : { encoding: 'utf-8', mode },\n );\n await runFileSystemOperationWithRetryAsync(() => fs.promises.rename(tempPath, filePath), { retryDelaysMs });\n } finally {\n await fs.promises.rm(tempPath, { force: true });\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkBA,MAAM,cAAc,OAAO,sBAAsB,cAAc,IAAI,kBAAkB,CAAC,IAAI,KAAA;AAC1F,MAAM,YAAY,cAAc,IAAI,WAAW,WAAW,IAAI,KAAA;AAC9D,MAAM,oDAAoC,IAAI,IAAI;CAAC;CAAU;CAAS;AAAO,CAAC;AAE9E,MAAa,sCAAsC;CAAC;CAAI;CAAI;CAAI;CAAK;CAAK;CAAK;CAAM;CAAM;AAAI;;;;;;;;;AAoB/F,SAAgB,uBAAuB,SAAuB;CAC5D,IAAI,WAAW,GAAG;CAClB,IAAI,WACF,IAAI;EACF,QAAQ,KAAK,WAAW,GAAG,GAAG,OAAO;EACrC;CACF,QAAQ,CAER;CAEF,MAAM,MAAM,KAAK,IAAI,IAAI;CACzB,OAAO,KAAK,IAAI,IAAI;AAGtB;AAUA,SAAgB,2BAA2B,OAAyB;CAClE,MAAM,OAAQ,OAA6C;CAC3D,OAAO,OAAO,SAAS,YAAY,kCAAkC,IAAI,IAAI;AAC/E;AAEA,SAAgB,gCAAmC,WAAoB,UAAkC,CAAC,GAAM;CAC9G,MAAM,gBAAgB,QAAQ,iBAAiB;CAC/C,MAAM,OAAO,QAAQ,QAAQ;CAC7B,KAAK,IAAI,UAAU,IAAK,WACtB,IAAI;EACF,OAAO,UAAU;CACnB,SAAS,OAAO;EACd,MAAM,UAAU,cAAc;EAC9B,IAAI,YAAY,KAAA,KAAa,CAAC,2BAA2B,KAAK,GAAG,MAAM;EACvE,KAAK,OAAO;CACd;AAEJ;;;;;;;;;;;;;;;;;;;;;;;;ACnDA,MAAM,oBAAoB;AAC1B,MAAM,cAAc;AAgBpB,SAAgB,uBACd,UAAmC,CAAC,GACS;CAC7C,MAAM,SAAS,QAAQ,MAAMA;CAC7B,MAAM,MAAM,QAAQ,OAAO,KAAK;CAChC,MAAM,MAAM,QAAQ,OAAO,QAAQ;CACnC,MAAM,SAAS,QAAQ,UAAU,KAAK;CACtC,MAAM,OAAO,QAAQ;CAErB,MAAM,oBAAoB,QAAQ,qBAAqB,QAAQ,aAAa;CAC5E,MAAM,uBAAuB,QAAQ,wBAAwB;CAC7D,MAAM,gBAAgB,QAAQ,iBAAiB;CAC/C,MAAM,sBAAsB,oBAAoB,gBAAgB,CAAC;CACjE,MAAM,yBAAyB,uBAAuB,gBAAgB,CAAC;CACvE,MAAM,OAAO,QAAQ,QAAQ;CAE7B,QAAQ,UAAkB,YAA0B;EAClD,sCACQ;GACJ,OAAO,UAAUC,UAAK,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;EAC9D,GACA;GAAE,eAAe;GAAwB;EAAK,CAChD;EACA,MAAM,WAAWA,UAAK,KACpBA,UAAK,QAAQ,QAAQ,GACrB,IAAIA,UAAK,SAAS,QAAQ,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,KAChF;EACA,IAAI;GACF,OAAO,cACL,UACA,KAAK,UAAU,SAAS,MAAM,WAAW,GACzC,SAAS,KAAA,IAAY,UAAU;IAAE,UAAU;IAAS;GAAK,CAC3D;GACA,sCACQ;IACJ,OAAO,WAAW,UAAU,QAAQ;GACtC,GACA;IAAE,eAAe;IAAqB;GAAK,CAC7C;EACF,UAAU;GAER,OAAO,OAAO,UAAU,EAAE,OAAO,KAAK,CAAC;EACzC;CACF;AACF;AAEA,MAAa,kBAAkB,uBAAuB;AACtD,MAAa,yBAAyB,uBAAuB,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
//#region src/services/fileSystemRetry/index.ts
|
|
4
|
+
/**
|
|
5
|
+
* Retry helpers for transient filesystem failures.
|
|
6
|
+
*
|
|
7
|
+
* Windows holds brief directory and rename locks (antivirus, indexers, another
|
|
8
|
+
* process reading the file), surfacing as EACCES, EBUSY or EPERM. Those clear on
|
|
9
|
+
* their own, so the operation is retried on a backoff instead of failing a run.
|
|
10
|
+
*
|
|
11
|
+
* DESIGN PATTERNS:
|
|
12
|
+
* - Two variants with the same policy: a synchronous one for the atomic writers
|
|
13
|
+
* that must not yield mid-write, and an async one for everything else
|
|
14
|
+
*
|
|
15
|
+
* AVOID:
|
|
16
|
+
* - Reaching for the synchronous variant on a hot path; it blocks the event
|
|
17
|
+
* loop, and every millisecond of backoff is a millisecond the parent cannot
|
|
18
|
+
* service a child. Prefer `runFileSystemOperationWithRetryAsync`
|
|
19
|
+
* - Retrying ENOENT or ENOTDIR; those are real errors, not contention
|
|
20
|
+
*/
|
|
21
|
+
const WAIT_BUFFER = typeof SharedArrayBuffer !== "undefined" ? new SharedArrayBuffer(4) : void 0;
|
|
22
|
+
const WAIT_VIEW = WAIT_BUFFER ? new Int32Array(WAIT_BUFFER) : void 0;
|
|
23
|
+
const RETRYABLE_FILE_SYSTEM_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
24
|
+
"EACCES",
|
|
25
|
+
"EBUSY",
|
|
26
|
+
"EPERM"
|
|
27
|
+
]);
|
|
28
|
+
const DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS = [
|
|
29
|
+
10,
|
|
30
|
+
25,
|
|
31
|
+
50,
|
|
32
|
+
100,
|
|
33
|
+
200,
|
|
34
|
+
500,
|
|
35
|
+
1e3,
|
|
36
|
+
2e3,
|
|
37
|
+
4e3
|
|
38
|
+
];
|
|
39
|
+
/**
|
|
40
|
+
* Block the current thread for `delayMs`.
|
|
41
|
+
*
|
|
42
|
+
* Only for synchronous callers that cannot yield. `Atomics.wait` parks the
|
|
43
|
+
* thread rather than burning CPU; the loop below is a portable fallback for
|
|
44
|
+
* runtimes without SharedArrayBuffer and does spin, which is why async callers
|
|
45
|
+
* must use `delayForFileSystemRetry` instead.
|
|
46
|
+
*/
|
|
47
|
+
function waitForFileSystemRetry(delayMs) {
|
|
48
|
+
if (delayMs <= 0) return;
|
|
49
|
+
if (WAIT_VIEW) try {
|
|
50
|
+
Atomics.wait(WAIT_VIEW, 0, 0, delayMs);
|
|
51
|
+
return;
|
|
52
|
+
} catch {}
|
|
53
|
+
const end = Date.now() + delayMs;
|
|
54
|
+
while (Date.now() < end);
|
|
55
|
+
}
|
|
56
|
+
function isRetryableFileSystemError(error) {
|
|
57
|
+
const code = error?.code;
|
|
58
|
+
return typeof code === "string" && RETRYABLE_FILE_SYSTEM_ERROR_CODES.has(code);
|
|
59
|
+
}
|
|
60
|
+
function runFileSystemOperationWithRetry(operation, options = {}) {
|
|
61
|
+
const retryDelaysMs = options.retryDelaysMs ?? DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS;
|
|
62
|
+
const wait = options.wait ?? waitForFileSystemRetry;
|
|
63
|
+
for (let attempt = 0;; attempt++) try {
|
|
64
|
+
return operation();
|
|
65
|
+
} catch (error) {
|
|
66
|
+
const delayMs = retryDelaysMs[attempt];
|
|
67
|
+
if (delayMs === void 0 || !isRetryableFileSystemError(error)) throw error;
|
|
68
|
+
wait(delayMs);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region src/services/atomicJson/index.ts
|
|
73
|
+
/**
|
|
74
|
+
* Atomic JSON writes.
|
|
75
|
+
*
|
|
76
|
+
* DESIGN PATTERNS:
|
|
77
|
+
* - Write to a uniquely named temp file in the destination directory, then
|
|
78
|
+
* rename onto the target. Rename is atomic within a filesystem, so a reader
|
|
79
|
+
* sees either the previous file or the complete new one, never a partial write
|
|
80
|
+
* - The temp name carries pid, timestamp and a random suffix so two processes
|
|
81
|
+
* writing the same target never collide on the temp file
|
|
82
|
+
*
|
|
83
|
+
* WHAT THIS DOES NOT DO:
|
|
84
|
+
* This prevents torn reads. It does NOT prevent lost updates: two writers doing
|
|
85
|
+
* read-modify-write still clobber each other, because the rename only publishes
|
|
86
|
+
* whatever the caller already computed. Anything needing mutual exclusion has to
|
|
87
|
+
* take a lease or claim, not rely on atomic writes.
|
|
88
|
+
*
|
|
89
|
+
* AVOID:
|
|
90
|
+
* - Treating this as a lock
|
|
91
|
+
* - Using the plain writer for anything sensitive; prefer writePrivateAtomicJson,
|
|
92
|
+
* which creates the file 0600 so a shared temp directory does not leak prompts
|
|
93
|
+
*/
|
|
94
|
+
const PRIVATE_FILE_MODE = 384;
|
|
95
|
+
const JSON_INDENT = 2;
|
|
96
|
+
function createAtomicJsonWriter(options = {}) {
|
|
97
|
+
const fsImpl = options.fs ?? fs;
|
|
98
|
+
const now = options.now ?? Date.now;
|
|
99
|
+
const pid = options.pid ?? process.pid;
|
|
100
|
+
const random = options.random ?? Math.random;
|
|
101
|
+
const mode = options.mode;
|
|
102
|
+
const retryRenameErrors = options.retryRenameErrors ?? process.platform === "win32";
|
|
103
|
+
const retryDirectoryErrors = options.retryDirectoryErrors ?? retryRenameErrors;
|
|
104
|
+
const retryDelaysMs = options.retryDelaysMs ?? DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS;
|
|
105
|
+
const renameRetryDelaysMs = retryRenameErrors ? retryDelaysMs : [];
|
|
106
|
+
const directoryRetryDelaysMs = retryDirectoryErrors ? retryDelaysMs : [];
|
|
107
|
+
const wait = options.wait ?? waitForFileSystemRetry;
|
|
108
|
+
return (filePath, payload) => {
|
|
109
|
+
runFileSystemOperationWithRetry(() => {
|
|
110
|
+
fsImpl.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
111
|
+
}, {
|
|
112
|
+
retryDelaysMs: directoryRetryDelaysMs,
|
|
113
|
+
wait
|
|
114
|
+
});
|
|
115
|
+
const tempPath = path.join(path.dirname(filePath), `.${path.basename(filePath)}.${pid}.${now()}.${random().toString(36).slice(2)}.tmp`);
|
|
116
|
+
try {
|
|
117
|
+
fsImpl.writeFileSync(tempPath, JSON.stringify(payload, null, JSON_INDENT), mode === void 0 ? "utf-8" : {
|
|
118
|
+
encoding: "utf-8",
|
|
119
|
+
mode
|
|
120
|
+
});
|
|
121
|
+
runFileSystemOperationWithRetry(() => {
|
|
122
|
+
fsImpl.renameSync(tempPath, filePath);
|
|
123
|
+
}, {
|
|
124
|
+
retryDelaysMs: renameRetryDelaysMs,
|
|
125
|
+
wait
|
|
126
|
+
});
|
|
127
|
+
} finally {
|
|
128
|
+
fsImpl.rmSync(tempPath, { force: true });
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
const writeAtomicJson = createAtomicJsonWriter();
|
|
133
|
+
const writePrivateAtomicJson = createAtomicJsonWriter({ mode: PRIVATE_FILE_MODE });
|
|
134
|
+
//#endregion
|
|
135
|
+
export { writePrivateAtomicJson as n, writeAtomicJson as t };
|
|
136
|
+
|
|
137
|
+
//# sourceMappingURL=atomicJson-D2t0CDh9.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"atomicJson-D2t0CDh9.mjs","names":[],"sources":["../src/services/fileSystemRetry/index.ts","../src/services/atomicJson/index.ts"],"sourcesContent":["/**\n * Retry helpers for transient filesystem failures.\n *\n * Windows holds brief directory and rename locks (antivirus, indexers, another\n * process reading the file), surfacing as EACCES, EBUSY or EPERM. Those clear on\n * their own, so the operation is retried on a backoff instead of failing a run.\n *\n * DESIGN PATTERNS:\n * - Two variants with the same policy: a synchronous one for the atomic writers\n * that must not yield mid-write, and an async one for everything else\n *\n * AVOID:\n * - Reaching for the synchronous variant on a hot path; it blocks the event\n * loop, and every millisecond of backoff is a millisecond the parent cannot\n * service a child. Prefer `runFileSystemOperationWithRetryAsync`\n * - Retrying ENOENT or ENOTDIR; those are real errors, not contention\n */\n\nconst WAIT_BUFFER = typeof SharedArrayBuffer !== 'undefined' ? new SharedArrayBuffer(4) : undefined;\nconst WAIT_VIEW = WAIT_BUFFER ? new Int32Array(WAIT_BUFFER) : undefined;\nconst RETRYABLE_FILE_SYSTEM_ERROR_CODES = new Set(['EACCES', 'EBUSY', 'EPERM']);\n\nexport const DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS = [10, 25, 50, 100, 200, 500, 1000, 2000, 4000] as const;\n\nexport type FileSystemRetryOptions = {\n retryDelaysMs?: readonly number[];\n wait?: (delayMs: number) => void;\n};\n\nexport type AsyncFileSystemRetryOptions = {\n retryDelaysMs?: readonly number[];\n wait?: (delayMs: number) => Promise<void>;\n};\n\n/**\n * Block the current thread for `delayMs`.\n *\n * Only for synchronous callers that cannot yield. `Atomics.wait` parks the\n * thread rather than burning CPU; the loop below is a portable fallback for\n * runtimes without SharedArrayBuffer and does spin, which is why async callers\n * must use `delayForFileSystemRetry` instead.\n */\nexport function waitForFileSystemRetry(delayMs: number): void {\n if (delayMs <= 0) return;\n if (WAIT_VIEW) {\n try {\n Atomics.wait(WAIT_VIEW, 0, 0, delayMs);\n return;\n } catch {\n // Fall through to the portable busy wait below.\n }\n }\n const end = Date.now() + delayMs;\n while (Date.now() < end) {\n // Portable fallback for runtimes where Atomics.wait is unavailable.\n }\n}\n\n/** Yield for `delayMs` without blocking the event loop. */\nexport function delayForFileSystemRetry(delayMs: number): Promise<void> {\n if (delayMs <= 0) return Promise.resolve();\n return new Promise((resolve) => {\n setTimeout(resolve, delayMs);\n });\n}\n\nexport function isRetryableFileSystemError(error: unknown): boolean {\n const code = (error as NodeJS.ErrnoException | undefined)?.code;\n return typeof code === 'string' && RETRYABLE_FILE_SYSTEM_ERROR_CODES.has(code);\n}\n\nexport function runFileSystemOperationWithRetry<T>(operation: () => T, options: FileSystemRetryOptions = {}): T {\n const retryDelaysMs = options.retryDelaysMs ?? DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS;\n const wait = options.wait ?? waitForFileSystemRetry;\n for (let attempt = 0; ; attempt++) {\n try {\n return operation();\n } catch (error) {\n const delayMs = retryDelaysMs[attempt];\n if (delayMs === undefined || !isRetryableFileSystemError(error)) throw error;\n wait(delayMs);\n }\n }\n}\n\nexport async function runFileSystemOperationWithRetryAsync<T>(\n operation: () => T | Promise<T>,\n options: AsyncFileSystemRetryOptions = {},\n): Promise<T> {\n const retryDelaysMs = options.retryDelaysMs ?? DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS;\n const wait = options.wait ?? delayForFileSystemRetry;\n for (let attempt = 0; ; attempt++) {\n try {\n return await operation();\n } catch (error) {\n const delayMs = retryDelaysMs[attempt];\n if (delayMs === undefined || !isRetryableFileSystemError(error)) throw error;\n await wait(delayMs);\n }\n }\n}\n","/**\n * Atomic JSON writes.\n *\n * DESIGN PATTERNS:\n * - Write to a uniquely named temp file in the destination directory, then\n * rename onto the target. Rename is atomic within a filesystem, so a reader\n * sees either the previous file or the complete new one, never a partial write\n * - The temp name carries pid, timestamp and a random suffix so two processes\n * writing the same target never collide on the temp file\n *\n * WHAT THIS DOES NOT DO:\n * This prevents torn reads. It does NOT prevent lost updates: two writers doing\n * read-modify-write still clobber each other, because the rename only publishes\n * whatever the caller already computed. Anything needing mutual exclusion has to\n * take a lease or claim, not rely on atomic writes.\n *\n * AVOID:\n * - Treating this as a lock\n * - Using the plain writer for anything sensitive; prefer writePrivateAtomicJson,\n * which creates the file 0600 so a shared temp directory does not leak prompts\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport {\n DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS,\n runFileSystemOperationWithRetry,\n runFileSystemOperationWithRetryAsync,\n waitForFileSystemRetry,\n} from '../fileSystemRetry';\n\nconst PRIVATE_FILE_MODE = 0o600;\nconst JSON_INDENT = 2;\n\ntype AtomicJsonFs = Pick<typeof fs, 'mkdirSync' | 'writeFileSync' | 'renameSync' | 'rmSync'>;\n\ntype AtomicJsonWriterOptions = {\n fs?: AtomicJsonFs;\n now?: () => number;\n pid?: number;\n random?: () => number;\n mode?: number;\n retryRenameErrors?: boolean;\n retryDirectoryErrors?: boolean;\n retryDelaysMs?: readonly number[];\n wait?: (delayMs: number) => void;\n};\n\nexport function createAtomicJsonWriter(\n options: AtomicJsonWriterOptions = {},\n): (filePath: string, payload: object) => void {\n const fsImpl = options.fs ?? fs;\n const now = options.now ?? Date.now;\n const pid = options.pid ?? process.pid;\n const random = options.random ?? Math.random;\n const mode = options.mode;\n // Retries exist for Windows directory and rename locks; other platforms fail fast.\n const retryRenameErrors = options.retryRenameErrors ?? process.platform === 'win32';\n const retryDirectoryErrors = options.retryDirectoryErrors ?? retryRenameErrors;\n const retryDelaysMs = options.retryDelaysMs ?? DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS;\n const renameRetryDelaysMs = retryRenameErrors ? retryDelaysMs : [];\n const directoryRetryDelaysMs = retryDirectoryErrors ? retryDelaysMs : [];\n const wait = options.wait ?? waitForFileSystemRetry;\n\n return (filePath: string, payload: object): void => {\n runFileSystemOperationWithRetry(\n () => {\n fsImpl.mkdirSync(path.dirname(filePath), { recursive: true });\n },\n { retryDelaysMs: directoryRetryDelaysMs, wait },\n );\n const tempPath = path.join(\n path.dirname(filePath),\n `.${path.basename(filePath)}.${pid}.${now()}.${random().toString(36).slice(2)}.tmp`,\n );\n try {\n fsImpl.writeFileSync(\n tempPath,\n JSON.stringify(payload, null, JSON_INDENT),\n mode === undefined ? 'utf-8' : { encoding: 'utf-8', mode },\n );\n runFileSystemOperationWithRetry(\n () => {\n fsImpl.renameSync(tempPath, filePath);\n },\n { retryDelaysMs: renameRetryDelaysMs, wait },\n );\n } finally {\n // Best effort: on the happy path the rename already consumed the temp file.\n fsImpl.rmSync(tempPath, { force: true });\n }\n };\n}\n\nexport const writeAtomicJson = createAtomicJsonWriter();\nexport const writePrivateAtomicJson = createAtomicJsonWriter({ mode: PRIVATE_FILE_MODE });\n\n/** Promise-based atomic writer for startup, polling, and other non-critical paths. */\nexport async function writeAtomicJsonAsync(filePath: string, payload: object, mode?: number): Promise<void> {\n const retryDelaysMs = process.platform === 'win32' ? DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS : [];\n await runFileSystemOperationWithRetryAsync(() => fs.promises.mkdir(path.dirname(filePath), { recursive: true }), {\n retryDelaysMs,\n });\n const tempPath = path.join(\n path.dirname(filePath),\n `.${path.basename(filePath)}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`,\n );\n try {\n await fs.promises.writeFile(\n tempPath,\n JSON.stringify(payload, null, JSON_INDENT),\n mode === undefined ? 'utf-8' : { encoding: 'utf-8', mode },\n );\n await runFileSystemOperationWithRetryAsync(() => fs.promises.rename(tempPath, filePath), { retryDelaysMs });\n } finally {\n await fs.promises.rm(tempPath, { force: true });\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAkBA,MAAM,cAAc,OAAO,sBAAsB,cAAc,IAAI,kBAAkB,CAAC,IAAI,KAAA;AAC1F,MAAM,YAAY,cAAc,IAAI,WAAW,WAAW,IAAI,KAAA;AAC9D,MAAM,oDAAoC,IAAI,IAAI;CAAC;CAAU;CAAS;AAAO,CAAC;AAE9E,MAAa,sCAAsC;CAAC;CAAI;CAAI;CAAI;CAAK;CAAK;CAAK;CAAM;CAAM;AAAI;;;;;;;;;AAoB/F,SAAgB,uBAAuB,SAAuB;CAC5D,IAAI,WAAW,GAAG;CAClB,IAAI,WACF,IAAI;EACF,QAAQ,KAAK,WAAW,GAAG,GAAG,OAAO;EACrC;CACF,QAAQ,CAER;CAEF,MAAM,MAAM,KAAK,IAAI,IAAI;CACzB,OAAO,KAAK,IAAI,IAAI;AAGtB;AAUA,SAAgB,2BAA2B,OAAyB;CAClE,MAAM,OAAQ,OAA6C;CAC3D,OAAO,OAAO,SAAS,YAAY,kCAAkC,IAAI,IAAI;AAC/E;AAEA,SAAgB,gCAAmC,WAAoB,UAAkC,CAAC,GAAM;CAC9G,MAAM,gBAAgB,QAAQ,iBAAiB;CAC/C,MAAM,OAAO,QAAQ,QAAQ;CAC7B,KAAK,IAAI,UAAU,IAAK,WACtB,IAAI;EACF,OAAO,UAAU;CACnB,SAAS,OAAO;EACd,MAAM,UAAU,cAAc;EAC9B,IAAI,YAAY,KAAA,KAAa,CAAC,2BAA2B,KAAK,GAAG,MAAM;EACvE,KAAK,OAAO;CACd;AAEJ;;;;;;;;;;;;;;;;;;;;;;;;ACnDA,MAAM,oBAAoB;AAC1B,MAAM,cAAc;AAgBpB,SAAgB,uBACd,UAAmC,CAAC,GACS;CAC7C,MAAM,SAAS,QAAQ,MAAM;CAC7B,MAAM,MAAM,QAAQ,OAAO,KAAK;CAChC,MAAM,MAAM,QAAQ,OAAO,QAAQ;CACnC,MAAM,SAAS,QAAQ,UAAU,KAAK;CACtC,MAAM,OAAO,QAAQ;CAErB,MAAM,oBAAoB,QAAQ,qBAAqB,QAAQ,aAAa;CAC5E,MAAM,uBAAuB,QAAQ,wBAAwB;CAC7D,MAAM,gBAAgB,QAAQ,iBAAiB;CAC/C,MAAM,sBAAsB,oBAAoB,gBAAgB,CAAC;CACjE,MAAM,yBAAyB,uBAAuB,gBAAgB,CAAC;CACvE,MAAM,OAAO,QAAQ,QAAQ;CAE7B,QAAQ,UAAkB,YAA0B;EAClD,sCACQ;GACJ,OAAO,UAAU,KAAK,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;EAC9D,GACA;GAAE,eAAe;GAAwB;EAAK,CAChD;EACA,MAAM,WAAW,KAAK,KACpB,KAAK,QAAQ,QAAQ,GACrB,IAAI,KAAK,SAAS,QAAQ,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,KAChF;EACA,IAAI;GACF,OAAO,cACL,UACA,KAAK,UAAU,SAAS,MAAM,WAAW,GACzC,SAAS,KAAA,IAAY,UAAU;IAAE,UAAU;IAAS;GAAK,CAC3D;GACA,sCACQ;IACJ,OAAO,WAAW,UAAU,QAAQ;GACtC,GACA;IAAE,eAAe;IAAqB;GAAK,CAC7C;EACF,UAAU;GAER,OAAO,OAAO,UAAU,EAAE,OAAO,KAAK,CAAC;EACzC;CACF;AACF;AAEA,MAAa,kBAAkB,uBAAuB;AACtD,MAAa,yBAAyB,uBAAuB,EAAE,MAAM,kBAAkB,CAAC"}
|