@remixhq/claude-plugin 0.1.22 → 0.1.23
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/.claude-plugin/plugin.json +1 -1
- package/agents/remix-collab.md +1 -1
- package/dist/hook-post-collab.cjs +5 -5
- package/dist/hook-post-collab.cjs.map +1 -1
- package/dist/hook-pre-git.cjs +2 -2
- package/dist/hook-pre-git.cjs.map +1 -1
- package/dist/hook-stop-collab.cjs +449 -65
- package/dist/hook-stop-collab.cjs.map +1 -1
- package/dist/hook-user-prompt.cjs +925 -487
- package/dist/hook-user-prompt.cjs.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.cjs +361 -486
- package/dist/mcp-server.cjs.map +1 -1
- package/package.json +3 -3
- package/skills/safe-collab-workflow/SKILL.md +3 -3
- package/skills/submit-change-step/SKILL.md +6 -5
- package/skills/sync-and-reconcile/SKILL.md +1 -1
|
@@ -38,7 +38,7 @@ var require_windows = __commonJS({
|
|
|
38
38
|
module2.exports = isexe;
|
|
39
39
|
isexe.sync = sync;
|
|
40
40
|
var fs11 = require("fs");
|
|
41
|
-
function checkPathExt(
|
|
41
|
+
function checkPathExt(path17, options) {
|
|
42
42
|
var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT;
|
|
43
43
|
if (!pathext) {
|
|
44
44
|
return true;
|
|
@@ -49,25 +49,25 @@ var require_windows = __commonJS({
|
|
|
49
49
|
}
|
|
50
50
|
for (var i2 = 0; i2 < pathext.length; i2++) {
|
|
51
51
|
var p = pathext[i2].toLowerCase();
|
|
52
|
-
if (p &&
|
|
52
|
+
if (p && path17.substr(-p.length).toLowerCase() === p) {
|
|
53
53
|
return true;
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
return false;
|
|
57
57
|
}
|
|
58
|
-
function checkStat(stat,
|
|
58
|
+
function checkStat(stat, path17, options) {
|
|
59
59
|
if (!stat.isSymbolicLink() && !stat.isFile()) {
|
|
60
60
|
return false;
|
|
61
61
|
}
|
|
62
|
-
return checkPathExt(
|
|
62
|
+
return checkPathExt(path17, options);
|
|
63
63
|
}
|
|
64
|
-
function isexe(
|
|
65
|
-
fs11.stat(
|
|
66
|
-
cb(er, er ? false : checkStat(stat,
|
|
64
|
+
function isexe(path17, options, cb) {
|
|
65
|
+
fs11.stat(path17, function(er, stat) {
|
|
66
|
+
cb(er, er ? false : checkStat(stat, path17, options));
|
|
67
67
|
});
|
|
68
68
|
}
|
|
69
|
-
function sync(
|
|
70
|
-
return checkStat(fs11.statSync(
|
|
69
|
+
function sync(path17, options) {
|
|
70
|
+
return checkStat(fs11.statSync(path17), path17, options);
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
});
|
|
@@ -79,13 +79,13 @@ var require_mode = __commonJS({
|
|
|
79
79
|
module2.exports = isexe;
|
|
80
80
|
isexe.sync = sync;
|
|
81
81
|
var fs11 = require("fs");
|
|
82
|
-
function isexe(
|
|
83
|
-
fs11.stat(
|
|
82
|
+
function isexe(path17, options, cb) {
|
|
83
|
+
fs11.stat(path17, function(er, stat) {
|
|
84
84
|
cb(er, er ? false : checkStat(stat, options));
|
|
85
85
|
});
|
|
86
86
|
}
|
|
87
|
-
function sync(
|
|
88
|
-
return checkStat(fs11.statSync(
|
|
87
|
+
function sync(path17, options) {
|
|
88
|
+
return checkStat(fs11.statSync(path17), options);
|
|
89
89
|
}
|
|
90
90
|
function checkStat(stat, options) {
|
|
91
91
|
return stat.isFile() && checkMode(stat, options);
|
|
@@ -119,7 +119,7 @@ var require_isexe = __commonJS({
|
|
|
119
119
|
}
|
|
120
120
|
module2.exports = isexe;
|
|
121
121
|
isexe.sync = sync;
|
|
122
|
-
function isexe(
|
|
122
|
+
function isexe(path17, options, cb) {
|
|
123
123
|
if (typeof options === "function") {
|
|
124
124
|
cb = options;
|
|
125
125
|
options = {};
|
|
@@ -129,7 +129,7 @@ var require_isexe = __commonJS({
|
|
|
129
129
|
throw new TypeError("callback not provided");
|
|
130
130
|
}
|
|
131
131
|
return new Promise(function(resolve, reject) {
|
|
132
|
-
isexe(
|
|
132
|
+
isexe(path17, options || {}, function(er, is) {
|
|
133
133
|
if (er) {
|
|
134
134
|
reject(er);
|
|
135
135
|
} else {
|
|
@@ -138,7 +138,7 @@ var require_isexe = __commonJS({
|
|
|
138
138
|
});
|
|
139
139
|
});
|
|
140
140
|
}
|
|
141
|
-
core(
|
|
141
|
+
core(path17, options || {}, function(er, is) {
|
|
142
142
|
if (er) {
|
|
143
143
|
if (er.code === "EACCES" || options && options.ignoreErrors) {
|
|
144
144
|
er = null;
|
|
@@ -148,9 +148,9 @@ var require_isexe = __commonJS({
|
|
|
148
148
|
cb(er, is);
|
|
149
149
|
});
|
|
150
150
|
}
|
|
151
|
-
function sync(
|
|
151
|
+
function sync(path17, options) {
|
|
152
152
|
try {
|
|
153
|
-
return core.sync(
|
|
153
|
+
return core.sync(path17, options || {});
|
|
154
154
|
} catch (er) {
|
|
155
155
|
if (options && options.ignoreErrors || er.code === "EACCES") {
|
|
156
156
|
return false;
|
|
@@ -167,7 +167,7 @@ var require_which = __commonJS({
|
|
|
167
167
|
"node_modules/which/which.js"(exports2, module2) {
|
|
168
168
|
"use strict";
|
|
169
169
|
var isWindows = process.platform === "win32" || process.env.OSTYPE === "cygwin" || process.env.OSTYPE === "msys";
|
|
170
|
-
var
|
|
170
|
+
var path17 = require("path");
|
|
171
171
|
var COLON = isWindows ? ";" : ":";
|
|
172
172
|
var isexe = require_isexe();
|
|
173
173
|
var getNotFoundError = (cmd) => Object.assign(new Error(`not found: ${cmd}`), { code: "ENOENT" });
|
|
@@ -205,7 +205,7 @@ var require_which = __commonJS({
|
|
|
205
205
|
return opt.all && found.length ? resolve(found) : reject(getNotFoundError(cmd));
|
|
206
206
|
const ppRaw = pathEnv[i2];
|
|
207
207
|
const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
|
|
208
|
-
const pCmd =
|
|
208
|
+
const pCmd = path17.join(pathPart, cmd);
|
|
209
209
|
const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
|
|
210
210
|
resolve(subStep(p, i2, 0));
|
|
211
211
|
});
|
|
@@ -232,7 +232,7 @@ var require_which = __commonJS({
|
|
|
232
232
|
for (let i2 = 0; i2 < pathEnv.length; i2++) {
|
|
233
233
|
const ppRaw = pathEnv[i2];
|
|
234
234
|
const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
|
|
235
|
-
const pCmd =
|
|
235
|
+
const pCmd = path17.join(pathPart, cmd);
|
|
236
236
|
const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
|
|
237
237
|
for (let j = 0; j < pathExt.length; j++) {
|
|
238
238
|
const cur = p + pathExt[j];
|
|
@@ -280,7 +280,7 @@ var require_path_key = __commonJS({
|
|
|
280
280
|
var require_resolveCommand = __commonJS({
|
|
281
281
|
"node_modules/cross-spawn/lib/util/resolveCommand.js"(exports2, module2) {
|
|
282
282
|
"use strict";
|
|
283
|
-
var
|
|
283
|
+
var path17 = require("path");
|
|
284
284
|
var which = require_which();
|
|
285
285
|
var getPathKey = require_path_key();
|
|
286
286
|
function resolveCommandAttempt(parsed, withoutPathExt) {
|
|
@@ -298,7 +298,7 @@ var require_resolveCommand = __commonJS({
|
|
|
298
298
|
try {
|
|
299
299
|
resolved = which.sync(parsed.command, {
|
|
300
300
|
path: env[getPathKey({ env })],
|
|
301
|
-
pathExt: withoutPathExt ?
|
|
301
|
+
pathExt: withoutPathExt ? path17.delimiter : void 0
|
|
302
302
|
});
|
|
303
303
|
} catch (e) {
|
|
304
304
|
} finally {
|
|
@@ -307,7 +307,7 @@ var require_resolveCommand = __commonJS({
|
|
|
307
307
|
}
|
|
308
308
|
}
|
|
309
309
|
if (resolved) {
|
|
310
|
-
resolved =
|
|
310
|
+
resolved = path17.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
|
|
311
311
|
}
|
|
312
312
|
return resolved;
|
|
313
313
|
}
|
|
@@ -361,8 +361,8 @@ var require_shebang_command = __commonJS({
|
|
|
361
361
|
if (!match) {
|
|
362
362
|
return null;
|
|
363
363
|
}
|
|
364
|
-
const [
|
|
365
|
-
const binary =
|
|
364
|
+
const [path17, argument] = match[0].replace(/#! ?/, "").split(" ");
|
|
365
|
+
const binary = path17.split("/").pop();
|
|
366
366
|
if (binary === "env") {
|
|
367
367
|
return argument;
|
|
368
368
|
}
|
|
@@ -397,7 +397,7 @@ var require_readShebang = __commonJS({
|
|
|
397
397
|
var require_parse = __commonJS({
|
|
398
398
|
"node_modules/cross-spawn/lib/parse.js"(exports2, module2) {
|
|
399
399
|
"use strict";
|
|
400
|
-
var
|
|
400
|
+
var path17 = require("path");
|
|
401
401
|
var resolveCommand = require_resolveCommand();
|
|
402
402
|
var escape = require_escape();
|
|
403
403
|
var readShebang = require_readShebang();
|
|
@@ -422,7 +422,7 @@ var require_parse = __commonJS({
|
|
|
422
422
|
const needsShell = !isExecutableRegExp.test(commandFile);
|
|
423
423
|
if (parsed.options.forceShell || needsShell) {
|
|
424
424
|
const needsDoubleEscapeMetaChars = isCmdShimRegExp.test(commandFile);
|
|
425
|
-
parsed.command =
|
|
425
|
+
parsed.command = path17.normalize(parsed.command);
|
|
426
426
|
parsed.command = escape.command(parsed.command);
|
|
427
427
|
parsed.args = parsed.args.map((arg) => escape.argument(arg, needsDoubleEscapeMetaChars));
|
|
428
428
|
const shellCommand = [parsed.command].concat(parsed.args).join(" ");
|
|
@@ -512,7 +512,7 @@ var require_cross_spawn = __commonJS({
|
|
|
512
512
|
var cp = require("child_process");
|
|
513
513
|
var parse = require_parse();
|
|
514
514
|
var enoent = require_enoent();
|
|
515
|
-
function
|
|
515
|
+
function spawn5(command, args, options) {
|
|
516
516
|
const parsed = parse(command, args, options);
|
|
517
517
|
const spawned = cp.spawn(parsed.command, parsed.args, parsed.options);
|
|
518
518
|
enoent.hookChildProcess(spawned, parsed);
|
|
@@ -524,8 +524,8 @@ var require_cross_spawn = __commonJS({
|
|
|
524
524
|
result.error = result.error || enoent.verifyENOENTSync(result.status, parsed);
|
|
525
525
|
return result;
|
|
526
526
|
}
|
|
527
|
-
module2.exports =
|
|
528
|
-
module2.exports.spawn =
|
|
527
|
+
module2.exports = spawn5;
|
|
528
|
+
module2.exports.spawn = spawn5;
|
|
529
529
|
module2.exports.sync = spawnSync2;
|
|
530
530
|
module2.exports._parse = parse;
|
|
531
531
|
module2.exports._enoent = enoent;
|
|
@@ -538,21 +538,27 @@ __export(hook_user_prompt_exports, {
|
|
|
538
538
|
runHookUserPrompt: () => runHookUserPrompt
|
|
539
539
|
});
|
|
540
540
|
module.exports = __toCommonJS(hook_user_prompt_exports);
|
|
541
|
-
var
|
|
542
|
-
var
|
|
543
|
-
var
|
|
541
|
+
var import_node_child_process8 = require("child_process");
|
|
542
|
+
var import_node_fs7 = require("fs");
|
|
543
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
544
544
|
|
|
545
|
-
// node_modules/@remixhq/core/dist/chunk-
|
|
545
|
+
// node_modules/@remixhq/core/dist/chunk-7XJGOKEO.js
|
|
546
546
|
var RemixError = class extends Error {
|
|
547
547
|
code;
|
|
548
548
|
exitCode;
|
|
549
549
|
hint;
|
|
550
|
+
// HTTP status code when this error originates from an API response.
|
|
551
|
+
// null for non-HTTP errors (validation, local IO, programming bugs).
|
|
552
|
+
// Callers use this to distinguish transient (5xx) from permanent (4xx)
|
|
553
|
+
// API failures without resorting to error-message string matching.
|
|
554
|
+
statusCode;
|
|
550
555
|
constructor(message, opts) {
|
|
551
556
|
super(message);
|
|
552
557
|
this.name = "RemixError";
|
|
553
558
|
this.code = opts?.code ?? null;
|
|
554
559
|
this.exitCode = opts?.exitCode ?? 1;
|
|
555
560
|
this.hint = opts?.hint ?? null;
|
|
561
|
+
this.statusCode = opts?.statusCode ?? null;
|
|
556
562
|
}
|
|
557
563
|
};
|
|
558
564
|
|
|
@@ -4937,13 +4943,13 @@ var logOutputSync = ({ serializedResult, fdNumber, state, verboseInfo, encoding,
|
|
|
4937
4943
|
}
|
|
4938
4944
|
};
|
|
4939
4945
|
var writeToFiles = (serializedResult, stdioItems, outputFiles) => {
|
|
4940
|
-
for (const { path:
|
|
4941
|
-
const pathString = typeof
|
|
4946
|
+
for (const { path: path17, append } of stdioItems.filter(({ type }) => FILE_TYPES.has(type))) {
|
|
4947
|
+
const pathString = typeof path17 === "string" ? path17 : path17.toString();
|
|
4942
4948
|
if (append || outputFiles.has(pathString)) {
|
|
4943
|
-
(0, import_node_fs4.appendFileSync)(
|
|
4949
|
+
(0, import_node_fs4.appendFileSync)(path17, serializedResult);
|
|
4944
4950
|
} else {
|
|
4945
4951
|
outputFiles.add(pathString);
|
|
4946
|
-
(0, import_node_fs4.writeFileSync)(
|
|
4952
|
+
(0, import_node_fs4.writeFileSync)(path17, serializedResult);
|
|
4947
4953
|
}
|
|
4948
4954
|
}
|
|
4949
4955
|
};
|
|
@@ -7331,7 +7337,7 @@ var {
|
|
|
7331
7337
|
getCancelSignal: getCancelSignal2
|
|
7332
7338
|
} = getIpcExport();
|
|
7333
7339
|
|
|
7334
|
-
// node_modules/@remixhq/core/dist/chunk-
|
|
7340
|
+
// node_modules/@remixhq/core/dist/chunk-S4ECO35X.js
|
|
7335
7341
|
async function runGit(args, cwd) {
|
|
7336
7342
|
const res = await execa("git", args, { cwd, stderr: "ignore" });
|
|
7337
7343
|
return String(res.stdout || "").trim();
|
|
@@ -7386,7 +7392,7 @@ function summarizeUnifiedDiff(diff) {
|
|
|
7386
7392
|
return { changedFilesCount, insertions, deletions };
|
|
7387
7393
|
}
|
|
7388
7394
|
|
|
7389
|
-
// node_modules/@remixhq/core/dist/chunk-
|
|
7395
|
+
// node_modules/@remixhq/core/dist/chunk-DBVN42RF.js
|
|
7390
7396
|
var import_promises12 = __toESM(require("fs/promises"), 1);
|
|
7391
7397
|
var import_path = __toESM(require("path"), 1);
|
|
7392
7398
|
var import_promises13 = __toESM(require("fs/promises"), 1);
|
|
@@ -7770,6 +7776,7 @@ var import_promises14 = __toESM(require("fs/promises"), 1);
|
|
|
7770
7776
|
var import_node_os4 = __toESM(require("os"), 1);
|
|
7771
7777
|
var import_node_path6 = __toESM(require("path"), 1);
|
|
7772
7778
|
var DEFERRED_TURN_SCHEMA_VERSION = 1;
|
|
7779
|
+
var DEFERRED_TURN_MAX_ATTEMPTS = 10;
|
|
7773
7780
|
var DEFERRED_TURN_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
7774
7781
|
var DEFERRED_TURN_DIR = "deferred-turns";
|
|
7775
7782
|
function stateRoot() {
|
|
@@ -7779,6 +7786,28 @@ function stateRoot() {
|
|
|
7779
7786
|
function getDeferredTurnDirPath() {
|
|
7780
7787
|
return import_node_path6.default.join(stateRoot(), DEFERRED_TURN_DIR);
|
|
7781
7788
|
}
|
|
7789
|
+
function deferredTurnFileName(sessionId, turnId) {
|
|
7790
|
+
const safe = (s) => s.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
7791
|
+
return `${safe(sessionId)}-${safe(turnId)}.json`;
|
|
7792
|
+
}
|
|
7793
|
+
function getDeferredTurnFilePath(sessionId, turnId) {
|
|
7794
|
+
return import_node_path6.default.join(getDeferredTurnDirPath(), deferredTurnFileName(sessionId, turnId));
|
|
7795
|
+
}
|
|
7796
|
+
async function writeDeferredTurn(record) {
|
|
7797
|
+
if (record.schemaVersion !== DEFERRED_TURN_SCHEMA_VERSION) {
|
|
7798
|
+
throw new Error(`writeDeferredTurn: unsupported schemaVersion ${record.schemaVersion}`);
|
|
7799
|
+
}
|
|
7800
|
+
if (!record.prompt.trim() || !record.assistantResponse.trim()) {
|
|
7801
|
+
throw new Error("writeDeferredTurn: prompt and assistantResponse must be non-empty");
|
|
7802
|
+
}
|
|
7803
|
+
const dir = getDeferredTurnDirPath();
|
|
7804
|
+
await import_promises14.default.mkdir(dir, { recursive: true });
|
|
7805
|
+
const filePath = getDeferredTurnFilePath(record.sessionId, record.turnId);
|
|
7806
|
+
const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
7807
|
+
await import_promises14.default.writeFile(tmpPath, JSON.stringify(record), "utf8");
|
|
7808
|
+
await import_promises14.default.rename(tmpPath, filePath);
|
|
7809
|
+
return filePath;
|
|
7810
|
+
}
|
|
7782
7811
|
async function readDeferredTurnFile(filePath) {
|
|
7783
7812
|
const raw = await import_promises14.default.readFile(filePath, "utf8").catch(() => null);
|
|
7784
7813
|
if (!raw) return null;
|
|
@@ -7791,7 +7820,7 @@ async function readDeferredTurnFile(filePath) {
|
|
|
7791
7820
|
if (!parsed || typeof parsed !== "object") return null;
|
|
7792
7821
|
const record = parsed;
|
|
7793
7822
|
if (record.schemaVersion !== DEFERRED_TURN_SCHEMA_VERSION) return null;
|
|
7794
|
-
if (typeof record.sessionId !== "string" || typeof record.turnId !== "string" || typeof record.repoRoot !== "string" || typeof record.prompt !== "string" || typeof record.assistantResponse !== "string" || typeof record.submittedAt !== "string" || typeof record.deferredAt !== "string" || record.reason !== "current_branch_unbound" && record.reason !== "recovery_in_progress") {
|
|
7823
|
+
if (typeof record.sessionId !== "string" || typeof record.turnId !== "string" || typeof record.repoRoot !== "string" || typeof record.prompt !== "string" || typeof record.assistantResponse !== "string" || typeof record.submittedAt !== "string" || typeof record.deferredAt !== "string" || record.reason !== "current_branch_unbound" && record.reason !== "recovery_in_progress" && record.reason !== "transient_recording_failure") {
|
|
7795
7824
|
return null;
|
|
7796
7825
|
}
|
|
7797
7826
|
return {
|
|
@@ -7804,7 +7833,17 @@ async function readDeferredTurnFile(filePath) {
|
|
|
7804
7833
|
submittedAt: record.submittedAt,
|
|
7805
7834
|
deferredAt: record.deferredAt,
|
|
7806
7835
|
reason: record.reason,
|
|
7807
|
-
branchAtDefer: typeof record.branchAtDefer === "string" || record.branchAtDefer === null ? record.branchAtDefer : null
|
|
7836
|
+
branchAtDefer: typeof record.branchAtDefer === "string" || record.branchAtDefer === null ? record.branchAtDefer : null,
|
|
7837
|
+
// Additive fields: pre-appId-aware records on disk won't have these
|
|
7838
|
+
// keys at all. Coerce missing/invalid to `null` (drainer treats
|
|
7839
|
+
// null as "legacy, drain as today" — see drainer for the policy).
|
|
7840
|
+
appIdAtDefer: typeof record.appIdAtDefer === "string" ? record.appIdAtDefer : null,
|
|
7841
|
+
projectIdAtDefer: typeof record.projectIdAtDefer === "string" ? record.projectIdAtDefer : null,
|
|
7842
|
+
// Pre-attemptCount records coerce to 0 — they've never been
|
|
7843
|
+
// counted against the cap, so giving them the cap's full budget
|
|
7844
|
+
// is correct (we'd rather over-retry a legacy record than drop it
|
|
7845
|
+
// unexpectedly). Negative or non-finite values also coerce to 0.
|
|
7846
|
+
attemptCount: typeof record.attemptCount === "number" && Number.isFinite(record.attemptCount) && record.attemptCount >= 0 ? Math.floor(record.attemptCount) : 0
|
|
7808
7847
|
};
|
|
7809
7848
|
}
|
|
7810
7849
|
async function listDeferredTurnsForRepo(repoRoot) {
|
|
@@ -7856,10 +7895,24 @@ async function pruneStaleDeferredTurns(maxAgeMs = DEFERRED_TURN_TTL_MS) {
|
|
|
7856
7895
|
}
|
|
7857
7896
|
return pruned;
|
|
7858
7897
|
}
|
|
7898
|
+
async function recordDeferredTurnFailedAttempt(filePath) {
|
|
7899
|
+
const current = await readDeferredTurnFile(filePath);
|
|
7900
|
+
if (!current) {
|
|
7901
|
+
return { promoted: true, finalAttemptCount: DEFERRED_TURN_MAX_ATTEMPTS };
|
|
7902
|
+
}
|
|
7903
|
+
const newAttemptCount = current.attemptCount + 1;
|
|
7904
|
+
if (newAttemptCount >= DEFERRED_TURN_MAX_ATTEMPTS) {
|
|
7905
|
+
await deleteDeferredTurnFile(filePath);
|
|
7906
|
+
return { promoted: true, finalAttemptCount: newAttemptCount };
|
|
7907
|
+
}
|
|
7908
|
+
const next = { ...current, attemptCount: newAttemptCount };
|
|
7909
|
+
await writeDeferredTurn(next);
|
|
7910
|
+
return { promoted: false, newAttemptCount };
|
|
7911
|
+
}
|
|
7859
7912
|
|
|
7860
7913
|
// src/deferred-turn-drainer.ts
|
|
7861
|
-
var
|
|
7862
|
-
var
|
|
7914
|
+
var import_promises23 = __toESM(require("fs/promises"), 1);
|
|
7915
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
7863
7916
|
var import_node_crypto3 = require("crypto");
|
|
7864
7917
|
|
|
7865
7918
|
// node_modules/@remixhq/core/dist/collab.js
|
|
@@ -7885,6 +7938,8 @@ function buildAppDeltaCacheKey(appId, payload) {
|
|
|
7885
7938
|
appId,
|
|
7886
7939
|
payload.baseHeadHash,
|
|
7887
7940
|
payload.targetHeadHash ?? "",
|
|
7941
|
+
payload.baseRevisionId ?? "",
|
|
7942
|
+
payload.targetRevisionId ?? "",
|
|
7888
7943
|
payload.localSnapshotHash ?? "",
|
|
7889
7944
|
payload.repoFingerprint ?? "",
|
|
7890
7945
|
payload.remoteUrl ?? "",
|
|
@@ -8131,11 +8186,11 @@ async function readLocalBaseline(params) {
|
|
|
8131
8186
|
const raw = await import_promises16.default.readFile(getBaselinePath(params), "utf8");
|
|
8132
8187
|
const parsed = JSON.parse(raw);
|
|
8133
8188
|
if (!parsed || typeof parsed !== "object") return null;
|
|
8134
|
-
if (parsed.schemaVersion
|
|
8189
|
+
if (![1, 2].includes(Number(parsed.schemaVersion)) || typeof parsed.key !== "string" || typeof parsed.repoRoot !== "string") {
|
|
8135
8190
|
return null;
|
|
8136
8191
|
}
|
|
8137
8192
|
return {
|
|
8138
|
-
schemaVersion: 1,
|
|
8193
|
+
schemaVersion: Number(parsed.schemaVersion) === 2 ? 2 : 1,
|
|
8139
8194
|
key: parsed.key,
|
|
8140
8195
|
repoRoot: parsed.repoRoot,
|
|
8141
8196
|
repoFingerprint: parsed.repoFingerprint ?? null,
|
|
@@ -8144,6 +8199,8 @@ async function readLocalBaseline(params) {
|
|
|
8144
8199
|
branchName: parsed.branchName ?? null,
|
|
8145
8200
|
lastSnapshotId: parsed.lastSnapshotId ?? null,
|
|
8146
8201
|
lastSnapshotHash: parsed.lastSnapshotHash ?? null,
|
|
8202
|
+
lastServerRevisionId: parsed.lastServerRevisionId ?? null,
|
|
8203
|
+
lastServerTreeHash: parsed.lastServerTreeHash ?? null,
|
|
8147
8204
|
lastServerHeadHash: parsed.lastServerHeadHash ?? null,
|
|
8148
8205
|
lastSeenLocalCommitHash: parsed.lastSeenLocalCommitHash ?? null,
|
|
8149
8206
|
updatedAt: String(parsed.updatedAt ?? "")
|
|
@@ -8155,7 +8212,7 @@ async function readLocalBaseline(params) {
|
|
|
8155
8212
|
async function writeLocalBaseline(baseline) {
|
|
8156
8213
|
const key = buildLaneStateKey(baseline);
|
|
8157
8214
|
const normalized = {
|
|
8158
|
-
schemaVersion:
|
|
8215
|
+
schemaVersion: 2,
|
|
8159
8216
|
key,
|
|
8160
8217
|
repoRoot: baseline.repoRoot,
|
|
8161
8218
|
repoFingerprint: baseline.repoFingerprint ?? null,
|
|
@@ -8164,6 +8221,8 @@ async function writeLocalBaseline(baseline) {
|
|
|
8164
8221
|
branchName: baseline.branchName ?? null,
|
|
8165
8222
|
lastSnapshotId: baseline.lastSnapshotId ?? null,
|
|
8166
8223
|
lastSnapshotHash: baseline.lastSnapshotHash ?? null,
|
|
8224
|
+
lastServerRevisionId: baseline.lastServerRevisionId ?? null,
|
|
8225
|
+
lastServerTreeHash: baseline.lastServerTreeHash ?? null,
|
|
8167
8226
|
lastServerHeadHash: baseline.lastServerHeadHash ?? null,
|
|
8168
8227
|
lastSeenLocalCommitHash: baseline.lastSeenLocalCommitHash ?? null,
|
|
8169
8228
|
updatedAt: baseline.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -8468,6 +8527,7 @@ function normalizeJob2(input) {
|
|
|
8468
8527
|
prompt: input.prompt,
|
|
8469
8528
|
assistantResponse: input.assistantResponse,
|
|
8470
8529
|
baselineSnapshotId: input.baselineSnapshotId ?? null,
|
|
8530
|
+
baselineServerRevisionId: input.baselineServerRevisionId ?? null,
|
|
8471
8531
|
baselineServerHeadHash: input.baselineServerHeadHash ?? null,
|
|
8472
8532
|
currentSnapshotId: input.currentSnapshotId,
|
|
8473
8533
|
capturedAt: input.capturedAt ?? now,
|
|
@@ -8502,6 +8562,7 @@ async function readPendingFinalizeJob(jobId) {
|
|
|
8502
8562
|
prompt: String(parsed.prompt ?? ""),
|
|
8503
8563
|
assistantResponse: String(parsed.assistantResponse ?? ""),
|
|
8504
8564
|
baselineSnapshotId: parsed.baselineSnapshotId ?? null,
|
|
8565
|
+
baselineServerRevisionId: parsed.baselineServerRevisionId ?? null,
|
|
8505
8566
|
baselineServerHeadHash: parsed.baselineServerHeadHash ?? null,
|
|
8506
8567
|
currentSnapshotId: String(parsed.currentSnapshotId ?? ""),
|
|
8507
8568
|
capturedAt: parsed.capturedAt,
|
|
@@ -9003,6 +9064,8 @@ function buildBaseState() {
|
|
|
9003
9064
|
branchName: null,
|
|
9004
9065
|
localCommitHash: null,
|
|
9005
9066
|
currentSnapshotHash: null,
|
|
9067
|
+
currentServerRevisionId: null,
|
|
9068
|
+
currentServerTreeHash: null,
|
|
9006
9069
|
currentServerHeadHash: null,
|
|
9007
9070
|
currentServerHeadCommitId: null,
|
|
9008
9071
|
worktreeClean: false,
|
|
@@ -9036,6 +9099,8 @@ function buildBaseState() {
|
|
|
9036
9099
|
baseline: {
|
|
9037
9100
|
lastSnapshotId: null,
|
|
9038
9101
|
lastSnapshotHash: null,
|
|
9102
|
+
lastServerRevisionId: null,
|
|
9103
|
+
lastServerTreeHash: null,
|
|
9039
9104
|
lastServerHeadHash: null,
|
|
9040
9105
|
lastSeenLocalCommitHash: null
|
|
9041
9106
|
}
|
|
@@ -9162,6 +9227,8 @@ async function collabDetectRepoState(params) {
|
|
|
9162
9227
|
summarizeAsyncJobs({ repoRoot, branchName: binding.branchName ?? null })
|
|
9163
9228
|
]);
|
|
9164
9229
|
const appHead = unwrapResponseObject(headResp, "app head");
|
|
9230
|
+
detected.currentServerRevisionId = appHead.headRevisionId ?? null;
|
|
9231
|
+
detected.currentServerTreeHash = appHead.treeHash ?? null;
|
|
9165
9232
|
detected.currentServerHeadHash = appHead.headCommitHash;
|
|
9166
9233
|
detected.currentServerHeadCommitId = appHead.headCommitId;
|
|
9167
9234
|
detected.currentSnapshotHash = inspection.snapshotHash;
|
|
@@ -9170,6 +9237,8 @@ async function collabDetectRepoState(params) {
|
|
|
9170
9237
|
detected.baseline = {
|
|
9171
9238
|
lastSnapshotId: baseline?.lastSnapshotId ?? null,
|
|
9172
9239
|
lastSnapshotHash: baseline?.lastSnapshotHash ?? null,
|
|
9240
|
+
lastServerRevisionId: baseline?.lastServerRevisionId ?? null,
|
|
9241
|
+
lastServerTreeHash: baseline?.lastServerTreeHash ?? null,
|
|
9173
9242
|
lastServerHeadHash: baseline?.lastServerHeadHash ?? null,
|
|
9174
9243
|
lastSeenLocalCommitHash: baseline?.lastSeenLocalCommitHash ?? null
|
|
9175
9244
|
};
|
|
@@ -9179,6 +9248,7 @@ async function collabDetectRepoState(params) {
|
|
|
9179
9248
|
const bootstrapResp = await params.api.getAppDelta(binding.currentAppId, {
|
|
9180
9249
|
baseHeadHash: localCommitHash,
|
|
9181
9250
|
targetHeadHash: appHead.headCommitHash,
|
|
9251
|
+
targetRevisionId: appHead.headRevisionId,
|
|
9182
9252
|
repoFingerprint: binding.repoFingerprint ?? void 0,
|
|
9183
9253
|
remoteUrl: binding.remoteUrl ?? void 0,
|
|
9184
9254
|
defaultBranch: binding.defaultBranch ?? void 0
|
|
@@ -9201,7 +9271,7 @@ async function collabDetectRepoState(params) {
|
|
|
9201
9271
|
}
|
|
9202
9272
|
}
|
|
9203
9273
|
detected.repoState = "external_local_base_changed";
|
|
9204
|
-
detected.hint = "No local Remix baseline exists for this lane yet. Run `remix collab
|
|
9274
|
+
detected.hint = "No local Remix revision baseline exists for this lane yet. Run `remix collab init` or sync this lane to seed the baseline.";
|
|
9205
9275
|
return detected;
|
|
9206
9276
|
}
|
|
9207
9277
|
const localHeadMovedSinceBaseline = Boolean(baseline.lastSeenLocalCommitHash) && localCommitHash !== baseline.lastSeenLocalCommitHash;
|
|
@@ -9220,7 +9290,30 @@ async function collabDetectRepoState(params) {
|
|
|
9220
9290
|
return detected;
|
|
9221
9291
|
}
|
|
9222
9292
|
const localChanged = inspection.snapshotHash !== baseline.lastSnapshotHash;
|
|
9223
|
-
const
|
|
9293
|
+
const serverHeadChanged = appHead.headCommitHash !== baseline.lastServerHeadHash;
|
|
9294
|
+
const revisionChanged = Boolean(
|
|
9295
|
+
baseline.lastServerRevisionId && (appHead.headRevisionId ?? null) !== baseline.lastServerRevisionId
|
|
9296
|
+
);
|
|
9297
|
+
const equivalentRevisionDrift = revisionChanged && !serverHeadChanged;
|
|
9298
|
+
if (equivalentRevisionDrift) {
|
|
9299
|
+
await writeLocalBaseline({
|
|
9300
|
+
repoRoot,
|
|
9301
|
+
repoFingerprint: binding.repoFingerprint,
|
|
9302
|
+
laneId: binding.laneId,
|
|
9303
|
+
currentAppId: binding.currentAppId,
|
|
9304
|
+
branchName: binding.branchName,
|
|
9305
|
+
lastSnapshotId: baseline.lastSnapshotId,
|
|
9306
|
+
lastSnapshotHash: baseline.lastSnapshotHash,
|
|
9307
|
+
lastServerRevisionId: appHead.headRevisionId ?? null,
|
|
9308
|
+
lastServerTreeHash: appHead.treeHash ?? baseline.lastServerTreeHash ?? null,
|
|
9309
|
+
lastServerHeadHash: appHead.headCommitHash,
|
|
9310
|
+
lastSeenLocalCommitHash: baseline.lastSeenLocalCommitHash
|
|
9311
|
+
});
|
|
9312
|
+
detected.baseline.lastServerRevisionId = appHead.headRevisionId ?? null;
|
|
9313
|
+
detected.baseline.lastServerTreeHash = appHead.treeHash ?? baseline.lastServerTreeHash ?? null;
|
|
9314
|
+
detected.baseline.lastServerHeadHash = appHead.headCommitHash;
|
|
9315
|
+
}
|
|
9316
|
+
const serverChanged = serverHeadChanged;
|
|
9224
9317
|
if (!localChanged && !serverChanged) {
|
|
9225
9318
|
detected.repoState = "idle";
|
|
9226
9319
|
return detected;
|
|
@@ -9644,6 +9737,7 @@ function buildWorkspaceMetadata(params) {
|
|
|
9644
9737
|
recordingMode: "boundary_delta",
|
|
9645
9738
|
baselineSnapshotId: params.baselineSnapshotId,
|
|
9646
9739
|
currentSnapshotId: params.currentSnapshotId,
|
|
9740
|
+
baselineServerRevisionId: params.baselineServerRevisionId ?? null,
|
|
9647
9741
|
baselineServerHeadHash: params.baselineServerHeadHash,
|
|
9648
9742
|
currentSnapshotHash: params.currentSnapshotHash,
|
|
9649
9743
|
localCommitHash: params.localCommitHash,
|
|
@@ -9722,12 +9816,12 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
9722
9816
|
throw buildFinalizeCliError({
|
|
9723
9817
|
message: "Local baseline is missing for this queued finalize job.",
|
|
9724
9818
|
exitCode: 2,
|
|
9725
|
-
hint: "Run `remix collab
|
|
9819
|
+
hint: "Run `remix collab init` to seed this checkout's revision baseline.",
|
|
9726
9820
|
disposition: "terminal",
|
|
9727
9821
|
reason: "baseline_missing"
|
|
9728
9822
|
});
|
|
9729
9823
|
}
|
|
9730
|
-
const baselineDrifted = baseline.lastSnapshotId !== job.baselineSnapshotId || baseline.lastServerHeadHash !== job.baselineServerHeadHash;
|
|
9824
|
+
const baselineDrifted = baseline.lastSnapshotId !== job.baselineSnapshotId || (job.baselineServerRevisionId ? baseline.lastServerRevisionId !== job.baselineServerRevisionId : false) || baseline.lastServerHeadHash !== job.baselineServerHeadHash;
|
|
9731
9825
|
const appHead = unwrapResponseObject(appHeadResp, "app head");
|
|
9732
9826
|
const remoteUrl = readMetadataString(job, "remoteUrl");
|
|
9733
9827
|
const defaultBranch = readMetadataString(job, "defaultBranch");
|
|
@@ -9750,12 +9844,13 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
9750
9844
|
throw buildFinalizeCliError({
|
|
9751
9845
|
message: "Finalize queue baseline drifted before this job was processed.",
|
|
9752
9846
|
exitCode: 1,
|
|
9753
|
-
hint: "Process queued finalize jobs in capture order, or
|
|
9847
|
+
hint: "Process queued finalize jobs in capture order, or run `remix collab init` to refresh the revision baseline before retrying.",
|
|
9754
9848
|
disposition: "terminal",
|
|
9755
9849
|
reason: "baseline_drifted"
|
|
9756
9850
|
});
|
|
9757
9851
|
}
|
|
9758
|
-
|
|
9852
|
+
const serverStillAtBaseline = job.baselineServerRevisionId ? appHead.headRevisionId === job.baselineServerRevisionId : appHead.headCommitHash === job.baselineServerHeadHash;
|
|
9853
|
+
if (!serverStillAtBaseline) {
|
|
9759
9854
|
throw buildFinalizeCliError({
|
|
9760
9855
|
message: "Server lane changed before a no-diff turn could be recorded.",
|
|
9761
9856
|
exitCode: 2,
|
|
@@ -9777,6 +9872,7 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
9777
9872
|
defaultBranch,
|
|
9778
9873
|
baselineSnapshotId: job.baselineSnapshotId,
|
|
9779
9874
|
currentSnapshotId: job.currentSnapshotId,
|
|
9875
|
+
baselineServerRevisionId: job.baselineServerRevisionId,
|
|
9780
9876
|
baselineServerHeadHash: job.baselineServerHeadHash,
|
|
9781
9877
|
currentSnapshotHash: snapshot.snapshotHash,
|
|
9782
9878
|
localCommitHash: snapshot.localCommitHash,
|
|
@@ -9797,6 +9893,8 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
9797
9893
|
branchName: job.branchName,
|
|
9798
9894
|
lastSnapshotId: snapshot.id,
|
|
9799
9895
|
lastSnapshotHash: snapshot.snapshotHash,
|
|
9896
|
+
lastServerRevisionId: appHead.headRevisionId ?? null,
|
|
9897
|
+
lastServerTreeHash: appHead.treeHash ?? null,
|
|
9800
9898
|
lastServerHeadHash: appHead.headCommitHash,
|
|
9801
9899
|
lastSeenLocalCommitHash: snapshot.localCommitHash
|
|
9802
9900
|
});
|
|
@@ -9817,14 +9915,14 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
9817
9915
|
};
|
|
9818
9916
|
}
|
|
9819
9917
|
const localBaselineAdvanced = baseline.lastSnapshotId !== job.baselineSnapshotId;
|
|
9820
|
-
const serverHeadAdvanced = appHead.headCommitHash !== job.baselineServerHeadHash;
|
|
9918
|
+
const serverHeadAdvanced = job.baselineServerRevisionId ? appHead.headRevisionId !== job.baselineServerRevisionId : appHead.headCommitHash !== job.baselineServerHeadHash;
|
|
9821
9919
|
if (baselineDrifted) {
|
|
9822
9920
|
const consistentAdvance = localBaselineAdvanced && serverHeadAdvanced;
|
|
9823
9921
|
if (!consistentAdvance) {
|
|
9824
9922
|
throw buildFinalizeCliError({
|
|
9825
9923
|
message: `Finalize queue baseline advanced inconsistently before this job was processed (localBaselineAdvanced=${localBaselineAdvanced}, serverHeadAdvanced=${serverHeadAdvanced}, jobBaselineSnapshotId=${job.baselineSnapshotId ?? "null"}, liveBaselineSnapshotId=${baseline.lastSnapshotId ?? "null"}, jobBaselineServerHeadHash=${job.baselineServerHeadHash ?? "null"}, liveBaselineServerHeadHash=${baseline.lastServerHeadHash ?? "null"}, currentAppHeadHash=${appHead.headCommitHash}). This indicates local Remix state diverged from the backend in a way that should not be reachable in normal operation; please report this as a bug.`,
|
|
9826
9924
|
exitCode: 1,
|
|
9827
|
-
hint: "Run `remix collab status` to inspect, then
|
|
9925
|
+
hint: "Run `remix collab status` to inspect, then sync or reconcile before retrying.",
|
|
9828
9926
|
disposition: "terminal",
|
|
9829
9927
|
reason: "baseline_drifted"
|
|
9830
9928
|
});
|
|
@@ -9832,6 +9930,7 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
9832
9930
|
}
|
|
9833
9931
|
let submissionDiff = diffResult.diff;
|
|
9834
9932
|
let submissionBaseHeadHash = job.baselineServerHeadHash;
|
|
9933
|
+
let submissionBaseRevisionId = job.baselineServerRevisionId;
|
|
9835
9934
|
let replayedFromBaseHash = null;
|
|
9836
9935
|
if (!submissionBaseHeadHash) {
|
|
9837
9936
|
throw buildFinalizeCliError({
|
|
@@ -9849,7 +9948,9 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
9849
9948
|
assistantResponse: job.assistantResponse,
|
|
9850
9949
|
diff: diffResult.diff,
|
|
9851
9950
|
baseCommitHash: submissionBaseHeadHash,
|
|
9951
|
+
baseRevisionId: job.baselineServerRevisionId,
|
|
9852
9952
|
targetHeadCommitHash: appHead.headCommitHash,
|
|
9953
|
+
targetRevisionId: appHead.headRevisionId,
|
|
9853
9954
|
expectedPaths: diffResult.changedPaths,
|
|
9854
9955
|
actor,
|
|
9855
9956
|
workspaceMetadata: buildWorkspaceMetadata({
|
|
@@ -9859,6 +9960,7 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
9859
9960
|
defaultBranch,
|
|
9860
9961
|
baselineSnapshotId: job.baselineSnapshotId,
|
|
9861
9962
|
currentSnapshotId: job.currentSnapshotId,
|
|
9963
|
+
baselineServerRevisionId: job.baselineServerRevisionId,
|
|
9862
9964
|
baselineServerHeadHash: job.baselineServerHeadHash,
|
|
9863
9965
|
currentSnapshotHash: snapshot.snapshotHash,
|
|
9864
9966
|
localCommitHash: snapshot.localCommitHash,
|
|
@@ -9884,6 +9986,7 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
9884
9986
|
submissionDiff = replayDiff.diff;
|
|
9885
9987
|
replayedFromBaseHash = submissionBaseHeadHash;
|
|
9886
9988
|
submissionBaseHeadHash = appHead.headCommitHash;
|
|
9989
|
+
submissionBaseRevisionId = appHead.headRevisionId;
|
|
9887
9990
|
} catch (error) {
|
|
9888
9991
|
if (error instanceof RemixError && error.finalizeDisposition === void 0) {
|
|
9889
9992
|
const detail = error.hint ? `${error.message} (${error.hint})` : error.message;
|
|
@@ -9905,6 +10008,7 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
9905
10008
|
assistantResponse: job.assistantResponse,
|
|
9906
10009
|
diff: submissionDiff,
|
|
9907
10010
|
baseCommitHash: submissionBaseHeadHash,
|
|
10011
|
+
baseRevisionId: submissionBaseRevisionId,
|
|
9908
10012
|
headCommitHash: submissionBaseHeadHash,
|
|
9909
10013
|
changedFilesCount: diffResult.stats.changedFilesCount,
|
|
9910
10014
|
insertions: diffResult.stats.insertions,
|
|
@@ -9917,6 +10021,7 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
9917
10021
|
defaultBranch,
|
|
9918
10022
|
baselineSnapshotId: job.baselineSnapshotId,
|
|
9919
10023
|
currentSnapshotId: job.currentSnapshotId,
|
|
10024
|
+
baselineServerRevisionId: job.baselineServerRevisionId,
|
|
9920
10025
|
baselineServerHeadHash: job.baselineServerHeadHash,
|
|
9921
10026
|
currentSnapshotHash: snapshot.snapshotHash,
|
|
9922
10027
|
localCommitHash: snapshot.localCommitHash,
|
|
@@ -9938,11 +10043,28 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
9938
10043
|
throw buildFinalizeCliError({
|
|
9939
10044
|
message: "Backend returned a succeeded change step without a head commit hash.",
|
|
9940
10045
|
exitCode: 1,
|
|
9941
|
-
hint: "This is a backend invariant violation; retry will not help.
|
|
10046
|
+
hint: "This is a backend invariant violation; retry will not help. Run `remix collab status` before trying again.",
|
|
9942
10047
|
disposition: "terminal",
|
|
9943
10048
|
reason: "missing_head_commit_hash"
|
|
9944
10049
|
});
|
|
9945
10050
|
}
|
|
10051
|
+
let nextServerRevisionId = typeof changeStep.resultRevisionId === "string" ? changeStep.resultRevisionId.trim() : "";
|
|
10052
|
+
let nextServerTreeHash = null;
|
|
10053
|
+
if (!nextServerRevisionId) {
|
|
10054
|
+
const freshHeadResp = await params.api.getAppHead(job.currentAppId);
|
|
10055
|
+
const freshHead = unwrapResponseObject(freshHeadResp, "app head");
|
|
10056
|
+
if (freshHead.headCommitHash !== nextServerHeadHash || !freshHead.headRevisionId) {
|
|
10057
|
+
throw buildFinalizeCliError({
|
|
10058
|
+
message: "Backend returned a succeeded change step without a matching result revision.",
|
|
10059
|
+
exitCode: 1,
|
|
10060
|
+
hint: "The local baseline was not advanced because the post-step revision could not be verified. Restart the backend/CLI and retry after checking `remix collab status`.",
|
|
10061
|
+
disposition: "terminal",
|
|
10062
|
+
reason: "missing_result_revision_id"
|
|
10063
|
+
});
|
|
10064
|
+
}
|
|
10065
|
+
nextServerRevisionId = freshHead.headRevisionId;
|
|
10066
|
+
nextServerTreeHash = freshHead.treeHash ?? null;
|
|
10067
|
+
}
|
|
9946
10068
|
await writeLocalBaseline({
|
|
9947
10069
|
repoRoot: job.repoRoot,
|
|
9948
10070
|
repoFingerprint: job.repoFingerprint,
|
|
@@ -9951,6 +10073,8 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
9951
10073
|
branchName: job.branchName,
|
|
9952
10074
|
lastSnapshotId: snapshot.id,
|
|
9953
10075
|
lastSnapshotHash: snapshot.snapshotHash,
|
|
10076
|
+
lastServerRevisionId: nextServerRevisionId,
|
|
10077
|
+
lastServerTreeHash: nextServerTreeHash,
|
|
9954
10078
|
lastServerHeadHash: nextServerHeadHash,
|
|
9955
10079
|
lastSeenLocalCommitHash: snapshot.localCommitHash
|
|
9956
10080
|
});
|
|
@@ -9982,6 +10106,7 @@ async function enqueueCapturedFinalizeTurn(params) {
|
|
|
9982
10106
|
prompt: params.prompt,
|
|
9983
10107
|
assistantResponse: params.assistantResponse,
|
|
9984
10108
|
baselineSnapshotId: params.baselineSnapshotId,
|
|
10109
|
+
baselineServerRevisionId: params.baselineServerRevisionId ?? null,
|
|
9985
10110
|
baselineServerHeadHash: params.baselineServerHeadHash,
|
|
9986
10111
|
currentSnapshotId: params.currentSnapshotId,
|
|
9987
10112
|
idempotencyKey: params.idempotencyKey,
|
|
@@ -10080,17 +10205,6 @@ async function collabFinalizeTurn(params) {
|
|
|
10080
10205
|
});
|
|
10081
10206
|
}
|
|
10082
10207
|
}
|
|
10083
|
-
const pendingReAnchor = await findPendingAsyncJob({
|
|
10084
|
-
repoRoot,
|
|
10085
|
-
branchName: binding.branchName ?? null,
|
|
10086
|
-
kind: "re_anchor"
|
|
10087
|
-
});
|
|
10088
|
-
if (pendingReAnchor) {
|
|
10089
|
-
throw new RemixError("Cannot finalize a turn while a re-anchor is still processing.", {
|
|
10090
|
-
exitCode: 2,
|
|
10091
|
-
hint: `Re-anchor job ${pendingReAnchor.id} is still in the background queue. Run \`remix collab status\` to check progress.`
|
|
10092
|
-
});
|
|
10093
|
-
}
|
|
10094
10208
|
const detected = await collabDetectRepoState({
|
|
10095
10209
|
api: params.api,
|
|
10096
10210
|
cwd: repoRoot,
|
|
@@ -10131,9 +10245,16 @@ async function collabFinalizeTurn(params) {
|
|
|
10131
10245
|
hint: detected.hint
|
|
10132
10246
|
});
|
|
10133
10247
|
}
|
|
10248
|
+
if (detected.repoState === "both_changed") {
|
|
10249
|
+
throw new RemixError("Local and server changes must be reconciled before finalizing this turn.", {
|
|
10250
|
+
code: "reconcile_required",
|
|
10251
|
+
exitCode: 2,
|
|
10252
|
+
hint: detected.hint || "Run `remix collab reconcile --dry-run` to inspect recovery options before retrying."
|
|
10253
|
+
});
|
|
10254
|
+
}
|
|
10134
10255
|
if (detected.repoState === "external_local_base_changed") {
|
|
10135
|
-
throw new RemixError("The local checkout
|
|
10136
|
-
code: "
|
|
10256
|
+
throw new RemixError("The local checkout is missing a Remix revision baseline for this lane.", {
|
|
10257
|
+
code: "baseline_missing",
|
|
10137
10258
|
exitCode: 2,
|
|
10138
10259
|
hint: detected.hint
|
|
10139
10260
|
});
|
|
@@ -10145,8 +10266,9 @@ async function collabFinalizeTurn(params) {
|
|
|
10145
10266
|
});
|
|
10146
10267
|
if (!baseline) {
|
|
10147
10268
|
throw new RemixError("Local Remix baseline is missing for this lane.", {
|
|
10269
|
+
code: "baseline_missing",
|
|
10148
10270
|
exitCode: 2,
|
|
10149
|
-
hint: "Run `remix collab
|
|
10271
|
+
hint: "Run `remix collab init` or sync this lane to create a fresh revision baseline."
|
|
10150
10272
|
});
|
|
10151
10273
|
}
|
|
10152
10274
|
const snapshot = await captureLocalSnapshot({
|
|
@@ -10157,10 +10279,11 @@ async function collabFinalizeTurn(params) {
|
|
|
10157
10279
|
});
|
|
10158
10280
|
const mode = snapshot.snapshotHash === baseline.lastSnapshotHash ? "no_diff_turn" : "changed_turn";
|
|
10159
10281
|
const idempotencyKey = params.idempotencyKey?.trim() || buildDeterministicIdempotencyKey({
|
|
10160
|
-
kind: "
|
|
10282
|
+
kind: "collab_finalize_turn_boundary_v2",
|
|
10161
10283
|
appId: binding.currentAppId,
|
|
10162
10284
|
laneId: binding.laneId,
|
|
10163
10285
|
baselineSnapshotId: baseline.lastSnapshotId,
|
|
10286
|
+
baselineServerRevisionId: baseline.lastServerRevisionId,
|
|
10164
10287
|
baselineServerHeadHash: baseline.lastServerHeadHash,
|
|
10165
10288
|
currentSnapshotId: snapshot.id,
|
|
10166
10289
|
currentSnapshotHash: snapshot.snapshotHash,
|
|
@@ -10180,6 +10303,7 @@ async function collabFinalizeTurn(params) {
|
|
|
10180
10303
|
prompt,
|
|
10181
10304
|
assistantResponse,
|
|
10182
10305
|
baselineSnapshotId: baseline.lastSnapshotId,
|
|
10306
|
+
baselineServerRevisionId: baseline.lastServerRevisionId,
|
|
10183
10307
|
baselineServerHeadHash: baseline.lastServerHeadHash,
|
|
10184
10308
|
currentSnapshotId: snapshot.id,
|
|
10185
10309
|
idempotencyKey,
|
|
@@ -10226,13 +10350,538 @@ var FINALIZE_PREFLIGHT_FAILURE_CODES = [
|
|
|
10226
10350
|
// Server has commits we don't. Fix: `remix collab sync` (safe to
|
|
10227
10351
|
// auto-run for fast-forward; non-FF refused by the command itself).
|
|
10228
10352
|
"pull_required",
|
|
10229
|
-
//
|
|
10230
|
-
|
|
10231
|
-
|
|
10353
|
+
// Both local and server changed. Fix: inspect and apply reconcile.
|
|
10354
|
+
"reconcile_required",
|
|
10355
|
+
// Local revision baseline is missing. Fix: `remix collab init` or sync.
|
|
10356
|
+
"baseline_missing"
|
|
10232
10357
|
];
|
|
10233
10358
|
var CODE_SET = new Set(FINALIZE_PREFLIGHT_FAILURE_CODES);
|
|
10359
|
+
function isFinalizePreflightFailureCode(value) {
|
|
10360
|
+
return typeof value === "string" && CODE_SET.has(value);
|
|
10361
|
+
}
|
|
10362
|
+
|
|
10363
|
+
// src/auto-fix-dispatcher.ts
|
|
10364
|
+
var import_node_child_process6 = require("child_process");
|
|
10365
|
+
var import_node_fs6 = require("fs");
|
|
10366
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
10367
|
+
|
|
10368
|
+
// src/finalize-failure-marker.ts
|
|
10369
|
+
var import_promises19 = __toESM(require("fs/promises"), 1);
|
|
10370
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
10371
|
+
var FINALIZE_FAILURE_MARKER_REL = import_node_path7.default.join(".remix", ".last-finalize-failure.json");
|
|
10372
|
+
function markerPath(repoRoot) {
|
|
10373
|
+
return import_node_path7.default.join(repoRoot, FINALIZE_FAILURE_MARKER_REL);
|
|
10374
|
+
}
|
|
10375
|
+
async function readFinalizeFailureMarker(repoRoot) {
|
|
10376
|
+
const raw = await import_promises19.default.readFile(markerPath(repoRoot), "utf8").catch(() => null);
|
|
10377
|
+
if (!raw) return null;
|
|
10378
|
+
try {
|
|
10379
|
+
const parsed = JSON.parse(raw);
|
|
10380
|
+
if (parsed.schemaVersion !== 1) return null;
|
|
10381
|
+
if (typeof parsed.repoRoot !== "string" || typeof parsed.failedAt !== "string") return null;
|
|
10382
|
+
if (typeof parsed.message !== "string") return null;
|
|
10383
|
+
return parsed;
|
|
10384
|
+
} catch {
|
|
10385
|
+
return null;
|
|
10386
|
+
}
|
|
10387
|
+
}
|
|
10388
|
+
async function writeFinalizeFailureMarker(marker) {
|
|
10389
|
+
const filePath = markerPath(marker.repoRoot);
|
|
10390
|
+
await import_promises19.default.mkdir(import_node_path7.default.dirname(filePath), { recursive: true });
|
|
10391
|
+
const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
10392
|
+
await import_promises19.default.writeFile(tmpPath, JSON.stringify(marker, null, 2), "utf8");
|
|
10393
|
+
await import_promises19.default.rename(tmpPath, filePath);
|
|
10394
|
+
}
|
|
10395
|
+
function buildFreshFailureMarker(params) {
|
|
10396
|
+
return {
|
|
10397
|
+
schemaVersion: 1,
|
|
10398
|
+
failedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10399
|
+
repoRoot: params.repoRoot,
|
|
10400
|
+
preflightCode: params.preflightCode,
|
|
10401
|
+
message: params.message,
|
|
10402
|
+
hint: params.hint,
|
|
10403
|
+
recommendedCommand: params.recommendedCommand,
|
|
10404
|
+
autoFix: {
|
|
10405
|
+
status: "not_attempted",
|
|
10406
|
+
command: null,
|
|
10407
|
+
pid: null,
|
|
10408
|
+
logPath: null,
|
|
10409
|
+
attemptedAt: null,
|
|
10410
|
+
failureMessage: null
|
|
10411
|
+
}
|
|
10412
|
+
};
|
|
10413
|
+
}
|
|
10414
|
+
|
|
10415
|
+
// src/hook-diagnostics.ts
|
|
10416
|
+
var import_node_crypto2 = require("crypto");
|
|
10417
|
+
var import_promises21 = __toESM(require("fs/promises"), 1);
|
|
10418
|
+
var import_node_os6 = __toESM(require("os"), 1);
|
|
10419
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
10420
|
+
|
|
10421
|
+
// src/hook-state.ts
|
|
10422
|
+
var import_promises20 = __toESM(require("fs/promises"), 1);
|
|
10423
|
+
var import_node_os5 = __toESM(require("os"), 1);
|
|
10424
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
10425
|
+
var import_node_crypto = require("crypto");
|
|
10426
|
+
function stateRoot2() {
|
|
10427
|
+
const configured = process.env.REMIX_CLAUDE_PLUGIN_HOOK_STATE_ROOT?.trim();
|
|
10428
|
+
return configured || import_node_path8.default.join(import_node_os5.default.tmpdir(), "remix-claude-plugin-hooks");
|
|
10429
|
+
}
|
|
10430
|
+
function statePath(sessionId) {
|
|
10431
|
+
return import_node_path8.default.join(stateRoot2(), `${sessionId}.json`);
|
|
10432
|
+
}
|
|
10433
|
+
function stateLockPath(sessionId) {
|
|
10434
|
+
return import_node_path8.default.join(stateRoot2(), `${sessionId}.lock`);
|
|
10435
|
+
}
|
|
10436
|
+
function stateLockMetaPath(sessionId) {
|
|
10437
|
+
return import_node_path8.default.join(stateLockPath(sessionId), "owner.json");
|
|
10438
|
+
}
|
|
10439
|
+
async function writeJsonAtomic2(filePath, value) {
|
|
10440
|
+
await import_promises20.default.mkdir(import_node_path8.default.dirname(filePath), { recursive: true });
|
|
10441
|
+
const tmpPath = `${filePath}.tmp-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
10442
|
+
await import_promises20.default.writeFile(tmpPath, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
10443
|
+
await import_promises20.default.rename(tmpPath, filePath);
|
|
10444
|
+
}
|
|
10445
|
+
var STATE_LOCK_WAIT_MS = 2e3;
|
|
10446
|
+
var STATE_LOCK_POLL_MS = 25;
|
|
10447
|
+
var STATE_LOCK_STALE_MS = 3e4;
|
|
10448
|
+
var STATE_LOCK_HEARTBEAT_MS = 5e3;
|
|
10449
|
+
async function sleep2(ms) {
|
|
10450
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
10451
|
+
}
|
|
10452
|
+
async function readStateLockMetadata(sessionId) {
|
|
10453
|
+
const raw = await import_promises20.default.readFile(stateLockMetaPath(sessionId), "utf8").catch(() => null);
|
|
10454
|
+
if (!raw) return null;
|
|
10455
|
+
try {
|
|
10456
|
+
const parsed = JSON.parse(raw);
|
|
10457
|
+
if (typeof parsed.ownerId !== "string" || typeof parsed.pid !== "number" || typeof parsed.createdAt !== "string" || typeof parsed.heartbeatAt !== "string") {
|
|
10458
|
+
return null;
|
|
10459
|
+
}
|
|
10460
|
+
return {
|
|
10461
|
+
ownerId: parsed.ownerId,
|
|
10462
|
+
pid: parsed.pid,
|
|
10463
|
+
createdAt: parsed.createdAt,
|
|
10464
|
+
heartbeatAt: parsed.heartbeatAt
|
|
10465
|
+
};
|
|
10466
|
+
} catch {
|
|
10467
|
+
return null;
|
|
10468
|
+
}
|
|
10469
|
+
}
|
|
10470
|
+
async function writeStateLockMetadata(sessionId, metadata) {
|
|
10471
|
+
await writeJsonAtomic2(stateLockMetaPath(sessionId), metadata);
|
|
10472
|
+
}
|
|
10473
|
+
async function tryRemoveStaleStateLock(sessionId) {
|
|
10474
|
+
const lockPath = stateLockPath(sessionId);
|
|
10475
|
+
const metadata = await readStateLockMetadata(sessionId);
|
|
10476
|
+
const staleByHeartbeat = metadata && Date.now() - new Date(metadata.heartbeatAt).getTime() > STATE_LOCK_STALE_MS;
|
|
10477
|
+
if (staleByHeartbeat) {
|
|
10478
|
+
await import_promises20.default.rm(lockPath, { recursive: true, force: true }).catch(() => void 0);
|
|
10479
|
+
return true;
|
|
10480
|
+
}
|
|
10481
|
+
if (!metadata) {
|
|
10482
|
+
const lockStat = await import_promises20.default.stat(lockPath).catch(() => null);
|
|
10483
|
+
if (lockStat && Date.now() - lockStat.mtimeMs > STATE_LOCK_STALE_MS) {
|
|
10484
|
+
await import_promises20.default.rm(lockPath, { recursive: true, force: true }).catch(() => void 0);
|
|
10485
|
+
return true;
|
|
10486
|
+
}
|
|
10487
|
+
}
|
|
10488
|
+
return false;
|
|
10489
|
+
}
|
|
10490
|
+
async function acquireStateLock(sessionId) {
|
|
10491
|
+
const lockPath = stateLockPath(sessionId);
|
|
10492
|
+
const deadline = Date.now() + STATE_LOCK_WAIT_MS;
|
|
10493
|
+
await import_promises20.default.mkdir(stateRoot2(), { recursive: true });
|
|
10494
|
+
while (true) {
|
|
10495
|
+
try {
|
|
10496
|
+
await import_promises20.default.mkdir(lockPath);
|
|
10497
|
+
const ownerId = (0, import_node_crypto.randomUUID)();
|
|
10498
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10499
|
+
const metadata = {
|
|
10500
|
+
ownerId,
|
|
10501
|
+
pid: process.pid,
|
|
10502
|
+
createdAt,
|
|
10503
|
+
heartbeatAt: createdAt
|
|
10504
|
+
};
|
|
10505
|
+
await writeStateLockMetadata(sessionId, metadata);
|
|
10506
|
+
let released = false;
|
|
10507
|
+
const heartbeat = setInterval(() => {
|
|
10508
|
+
if (released) return;
|
|
10509
|
+
void writeStateLockMetadata(sessionId, {
|
|
10510
|
+
...metadata,
|
|
10511
|
+
heartbeatAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10512
|
+
}).catch(() => void 0);
|
|
10513
|
+
}, STATE_LOCK_HEARTBEAT_MS);
|
|
10514
|
+
heartbeat.unref?.();
|
|
10515
|
+
return async () => {
|
|
10516
|
+
if (released) return;
|
|
10517
|
+
released = true;
|
|
10518
|
+
clearInterval(heartbeat);
|
|
10519
|
+
const currentMetadata = await readStateLockMetadata(sessionId);
|
|
10520
|
+
if (currentMetadata?.ownerId === ownerId) {
|
|
10521
|
+
await import_promises20.default.rm(lockPath, { recursive: true, force: true }).catch(() => void 0);
|
|
10522
|
+
}
|
|
10523
|
+
};
|
|
10524
|
+
} catch (error) {
|
|
10525
|
+
const code = error && typeof error === "object" && "code" in error ? error.code : null;
|
|
10526
|
+
if (code !== "EEXIST") {
|
|
10527
|
+
throw error;
|
|
10528
|
+
}
|
|
10529
|
+
if (await tryRemoveStaleStateLock(sessionId)) {
|
|
10530
|
+
continue;
|
|
10531
|
+
}
|
|
10532
|
+
if (Date.now() >= deadline) {
|
|
10533
|
+
throw new Error(`Timed out acquiring hook state lock for session ${sessionId}.`);
|
|
10534
|
+
}
|
|
10535
|
+
await sleep2(STATE_LOCK_POLL_MS);
|
|
10536
|
+
}
|
|
10537
|
+
}
|
|
10538
|
+
}
|
|
10539
|
+
async function withStateLock(sessionId, fn) {
|
|
10540
|
+
const release = await acquireStateLock(sessionId);
|
|
10541
|
+
try {
|
|
10542
|
+
return await fn();
|
|
10543
|
+
} finally {
|
|
10544
|
+
await release();
|
|
10545
|
+
}
|
|
10546
|
+
}
|
|
10547
|
+
async function savePendingTurnState(state) {
|
|
10548
|
+
await writeJsonAtomic2(statePath(state.sessionId), state);
|
|
10549
|
+
}
|
|
10550
|
+
async function createPendingTurnState(params) {
|
|
10551
|
+
return withStateLock(params.sessionId, async () => {
|
|
10552
|
+
const state = {
|
|
10553
|
+
sessionId: params.sessionId,
|
|
10554
|
+
turnId: (0, import_node_crypto.randomUUID)(),
|
|
10555
|
+
prompt: params.prompt,
|
|
10556
|
+
initialCwd: params.initialCwd?.trim() || null,
|
|
10557
|
+
intent: params.intent,
|
|
10558
|
+
submittedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10559
|
+
consultedMemory: false,
|
|
10560
|
+
touchedRepos: {},
|
|
10561
|
+
turnFailureMessage: null,
|
|
10562
|
+
turnFailureHint: null,
|
|
10563
|
+
turnFailedAt: null
|
|
10564
|
+
};
|
|
10565
|
+
await savePendingTurnState(state);
|
|
10566
|
+
return state;
|
|
10567
|
+
});
|
|
10568
|
+
}
|
|
10569
|
+
|
|
10570
|
+
// package.json
|
|
10571
|
+
var package_default = {
|
|
10572
|
+
name: "@remixhq/claude-plugin",
|
|
10573
|
+
version: "0.1.23",
|
|
10574
|
+
description: "Claude Code plugin for Remix collaboration workflows",
|
|
10575
|
+
homepage: "https://github.com/RemixDotOne/remix-claude-plugin",
|
|
10576
|
+
license: "MIT",
|
|
10577
|
+
repository: {
|
|
10578
|
+
type: "git",
|
|
10579
|
+
url: "https://github.com/RemixDotOne/remix-claude-plugin.git"
|
|
10580
|
+
},
|
|
10581
|
+
type: "module",
|
|
10582
|
+
engines: {
|
|
10583
|
+
node: ">=20"
|
|
10584
|
+
},
|
|
10585
|
+
publishConfig: {
|
|
10586
|
+
access: "public"
|
|
10587
|
+
},
|
|
10588
|
+
files: [
|
|
10589
|
+
"dist",
|
|
10590
|
+
".claude-plugin/plugin.json",
|
|
10591
|
+
".mcp.json",
|
|
10592
|
+
"skills",
|
|
10593
|
+
"hooks",
|
|
10594
|
+
"agents"
|
|
10595
|
+
],
|
|
10596
|
+
exports: {
|
|
10597
|
+
".": {
|
|
10598
|
+
types: "./dist/index.d.ts",
|
|
10599
|
+
import: "./dist/index.js"
|
|
10600
|
+
}
|
|
10601
|
+
},
|
|
10602
|
+
scripts: {
|
|
10603
|
+
build: "tsup",
|
|
10604
|
+
postbuild: `node -e "const fs=require('node:fs'); for (const p of ['dist/mcp-server.cjs','dist/hook-pre-git.cjs','dist/hook-user-prompt.cjs','dist/hook-post-collab.cjs','dist/hook-stop-collab.cjs']) fs.chmodSync(p, 0o755);"`,
|
|
10605
|
+
dev: "tsx src/mcp-server.ts",
|
|
10606
|
+
typecheck: "tsc -p tsconfig.json --noEmit",
|
|
10607
|
+
test: "node --import tsx --test 'src/**/*.test.ts'",
|
|
10608
|
+
prepack: "npm run build"
|
|
10609
|
+
},
|
|
10610
|
+
dependencies: {
|
|
10611
|
+
"@remixhq/core": "^0.1.18",
|
|
10612
|
+
"@remixhq/mcp": "^0.1.18"
|
|
10613
|
+
},
|
|
10614
|
+
devDependencies: {
|
|
10615
|
+
"@types/node": "^25.4.0",
|
|
10616
|
+
tsup: "^8.5.1",
|
|
10617
|
+
tsx: "^4.21.0",
|
|
10618
|
+
typescript: "^5.9.3"
|
|
10619
|
+
}
|
|
10620
|
+
};
|
|
10621
|
+
|
|
10622
|
+
// src/metadata.ts
|
|
10623
|
+
var pluginMetadata = {
|
|
10624
|
+
name: package_default.name,
|
|
10625
|
+
version: package_default.version,
|
|
10626
|
+
description: package_default.description,
|
|
10627
|
+
pluginId: "remix",
|
|
10628
|
+
agentName: "remix-collab"
|
|
10629
|
+
};
|
|
10234
10630
|
|
|
10235
|
-
//
|
|
10631
|
+
// src/hook-diagnostics.ts
|
|
10632
|
+
var MAX_LOG_BYTES = 512 * 1024;
|
|
10633
|
+
function resolveClaudeRoot() {
|
|
10634
|
+
const configured = process.env.CLAUDE_CONFIG_DIR?.trim();
|
|
10635
|
+
return configured || import_node_path9.default.join(import_node_os6.default.homedir(), ".claude");
|
|
10636
|
+
}
|
|
10637
|
+
function resolvePluginDataDirName() {
|
|
10638
|
+
return `${pluginMetadata.pluginId}-${pluginMetadata.pluginId}`;
|
|
10639
|
+
}
|
|
10640
|
+
function getHookDiagnosticsDirPath() {
|
|
10641
|
+
const configured = process.env.REMIX_CLAUDE_PLUGIN_HOOK_DIAGNOSTICS_DIR?.trim();
|
|
10642
|
+
return configured || import_node_path9.default.join(resolveClaudeRoot(), "plugins", "data", resolvePluginDataDirName());
|
|
10643
|
+
}
|
|
10644
|
+
function getHookDiagnosticsLogPath() {
|
|
10645
|
+
return import_node_path9.default.join(getHookDiagnosticsDirPath(), "hooks.ndjson");
|
|
10646
|
+
}
|
|
10647
|
+
function toFieldValue(value) {
|
|
10648
|
+
if (value === null) return null;
|
|
10649
|
+
if (typeof value === "string") return value;
|
|
10650
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
10651
|
+
if (typeof value === "boolean") return value;
|
|
10652
|
+
return void 0;
|
|
10653
|
+
}
|
|
10654
|
+
function normalizeFields(fields) {
|
|
10655
|
+
if (!fields) return {};
|
|
10656
|
+
const normalizedEntries = Object.entries(fields).map(([key, value]) => {
|
|
10657
|
+
const normalized = toFieldValue(value);
|
|
10658
|
+
return normalized === void 0 ? null : [key, normalized];
|
|
10659
|
+
}).filter((entry) => entry !== null);
|
|
10660
|
+
return Object.fromEntries(normalizedEntries);
|
|
10661
|
+
}
|
|
10662
|
+
async function rotateLogIfNeeded(logPath) {
|
|
10663
|
+
const stat = await import_promises21.default.stat(logPath).catch(() => null);
|
|
10664
|
+
if (!stat || stat.size < MAX_LOG_BYTES) {
|
|
10665
|
+
return;
|
|
10666
|
+
}
|
|
10667
|
+
const rotatedPath = `${logPath}.1`;
|
|
10668
|
+
await import_promises21.default.rm(rotatedPath, { force: true }).catch(() => void 0);
|
|
10669
|
+
await import_promises21.default.rename(logPath, rotatedPath).catch(() => void 0);
|
|
10670
|
+
}
|
|
10671
|
+
function summarizeText(value) {
|
|
10672
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
10673
|
+
return {
|
|
10674
|
+
present: false,
|
|
10675
|
+
length: 0,
|
|
10676
|
+
sha256Prefix: null
|
|
10677
|
+
};
|
|
10678
|
+
}
|
|
10679
|
+
const trimmed = value.trim();
|
|
10680
|
+
return {
|
|
10681
|
+
present: true,
|
|
10682
|
+
length: trimmed.length,
|
|
10683
|
+
sha256Prefix: (0, import_node_crypto2.createHash)("sha256").update(trimmed).digest("hex").slice(0, 12)
|
|
10684
|
+
};
|
|
10685
|
+
}
|
|
10686
|
+
async function appendHookDiagnosticsEvent(params) {
|
|
10687
|
+
try {
|
|
10688
|
+
const logPath = getHookDiagnosticsLogPath();
|
|
10689
|
+
await import_promises21.default.mkdir(import_node_path9.default.dirname(logPath), { recursive: true });
|
|
10690
|
+
await rotateLogIfNeeded(logPath);
|
|
10691
|
+
const event = {
|
|
10692
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10693
|
+
hook: params.hook,
|
|
10694
|
+
pluginVersion: pluginMetadata.version,
|
|
10695
|
+
pid: process.pid,
|
|
10696
|
+
sessionId: params.sessionId?.trim() || null,
|
|
10697
|
+
turnId: params.turnId?.trim() || null,
|
|
10698
|
+
stage: params.stage.trim(),
|
|
10699
|
+
result: params.result,
|
|
10700
|
+
reason: params.reason?.trim() || null,
|
|
10701
|
+
toolName: params.toolName?.trim() || null,
|
|
10702
|
+
repoRoot: params.repoRoot?.trim() || null,
|
|
10703
|
+
message: params.message?.trim() || null,
|
|
10704
|
+
fields: normalizeFields(params.fields)
|
|
10705
|
+
};
|
|
10706
|
+
await import_promises21.default.appendFile(logPath, `${JSON.stringify(event)}
|
|
10707
|
+
`, "utf8");
|
|
10708
|
+
} catch {
|
|
10709
|
+
}
|
|
10710
|
+
}
|
|
10711
|
+
|
|
10712
|
+
// src/auto-fix-dispatcher.ts
|
|
10713
|
+
var AUTO_FIX_COMMAND = {
|
|
10714
|
+
// Already auto-spawned by hook-user-prompt's branch-init path, but we
|
|
10715
|
+
// include it here too so a finalize-time failure (e.g. binding got
|
|
10716
|
+
// deleted between init and the next finalize) also self-heals.
|
|
10717
|
+
branch_binding_missing: ["collab", "init"],
|
|
10718
|
+
// Local revision baseline is missing. Init seeds the branch/lane baseline
|
|
10719
|
+
// without requiring the user to know about the recording internals.
|
|
10720
|
+
baseline_missing: ["collab", "init"],
|
|
10721
|
+
// Server moved ahead. `collab sync` is fast-forward-safe by default;
|
|
10722
|
+
// it refuses non-FF on its own, so we don't need to gate here.
|
|
10723
|
+
pull_required: ["collab", "sync"]
|
|
10724
|
+
};
|
|
10725
|
+
var RECOMMENDED_USER_COMMAND = {
|
|
10726
|
+
not_bound: "remix collab init",
|
|
10727
|
+
branch_binding_missing: "remix collab init",
|
|
10728
|
+
family_ambiguous: "remix collab status",
|
|
10729
|
+
metadata_conflict: "remix collab status",
|
|
10730
|
+
branch_mismatch: "remix collab status",
|
|
10731
|
+
missing_head: "remix collab status",
|
|
10732
|
+
remote_error: "remix collab status",
|
|
10733
|
+
pull_required: "remix collab sync",
|
|
10734
|
+
baseline_missing: "remix collab init"
|
|
10735
|
+
};
|
|
10736
|
+
var SPAWN_LOCK_REL = (cmdSlug) => import_node_path10.default.join(".remix", `.${cmdSlug}-spawning`);
|
|
10737
|
+
var SPAWN_LOG_REL = (cmdSlug) => import_node_path10.default.join(".remix", `${cmdSlug}.log`);
|
|
10738
|
+
var SPAWN_THROTTLE_MS = 5 * 60 * 1e3;
|
|
10739
|
+
function commandSlug(args) {
|
|
10740
|
+
return args.join("-").replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
10741
|
+
}
|
|
10742
|
+
function spawnFixDetached(repoRoot, args) {
|
|
10743
|
+
const slug = commandSlug(args);
|
|
10744
|
+
const command = `remix ${args.join(" ")}`;
|
|
10745
|
+
const remixDir = import_node_path10.default.join(repoRoot, ".remix");
|
|
10746
|
+
const lockPath = import_node_path10.default.join(repoRoot, SPAWN_LOCK_REL(slug));
|
|
10747
|
+
const logPath = import_node_path10.default.join(repoRoot, SPAWN_LOG_REL(slug));
|
|
10748
|
+
try {
|
|
10749
|
+
if ((0, import_node_fs6.existsSync)(lockPath)) {
|
|
10750
|
+
const ageMs = Date.now() - (0, import_node_fs6.statSync)(lockPath).mtimeMs;
|
|
10751
|
+
if (ageMs < SPAWN_THROTTLE_MS) {
|
|
10752
|
+
return { kind: "spawn_throttled", command, reason: "spawn_lock_held" };
|
|
10753
|
+
}
|
|
10754
|
+
}
|
|
10755
|
+
} catch {
|
|
10756
|
+
}
|
|
10757
|
+
try {
|
|
10758
|
+
(0, import_node_fs6.mkdirSync)(remixDir, { recursive: true });
|
|
10759
|
+
} catch {
|
|
10760
|
+
}
|
|
10761
|
+
let out;
|
|
10762
|
+
let err;
|
|
10763
|
+
try {
|
|
10764
|
+
out = (0, import_node_fs6.openSync)(logPath, "a");
|
|
10765
|
+
err = (0, import_node_fs6.openSync)(logPath, "a");
|
|
10766
|
+
} catch (logErr) {
|
|
10767
|
+
return {
|
|
10768
|
+
kind: "spawn_failed",
|
|
10769
|
+
command,
|
|
10770
|
+
reason: "log_open_failed",
|
|
10771
|
+
message: logErr instanceof Error ? logErr.message : String(logErr)
|
|
10772
|
+
};
|
|
10773
|
+
}
|
|
10774
|
+
try {
|
|
10775
|
+
const child = (0, import_node_child_process6.spawn)("remix", [...args], {
|
|
10776
|
+
cwd: repoRoot,
|
|
10777
|
+
detached: true,
|
|
10778
|
+
stdio: ["ignore", out, err],
|
|
10779
|
+
env: { ...process.env, REMIX_AUTO_FIX_SPAWN: "1" }
|
|
10780
|
+
});
|
|
10781
|
+
child.unref();
|
|
10782
|
+
try {
|
|
10783
|
+
(0, import_node_fs6.writeFileSync)(lockPath, String(child.pid ?? ""), "utf8");
|
|
10784
|
+
(0, import_node_fs6.utimesSync)(lockPath, /* @__PURE__ */ new Date(), /* @__PURE__ */ new Date());
|
|
10785
|
+
} catch {
|
|
10786
|
+
}
|
|
10787
|
+
return { kind: "spawned", command, pid: child.pid, logPath };
|
|
10788
|
+
} catch (spawnErr) {
|
|
10789
|
+
return {
|
|
10790
|
+
kind: "spawn_failed",
|
|
10791
|
+
command,
|
|
10792
|
+
reason: "spawn_failed",
|
|
10793
|
+
message: spawnErr instanceof Error ? spawnErr.message : String(spawnErr)
|
|
10794
|
+
};
|
|
10795
|
+
}
|
|
10796
|
+
}
|
|
10797
|
+
async function dispatchFinalizeFailure(input) {
|
|
10798
|
+
const recommendedCommand = input.preflightCode ? RECOMMENDED_USER_COMMAND[input.preflightCode] ?? null : null;
|
|
10799
|
+
const marker = buildFreshFailureMarker({
|
|
10800
|
+
repoRoot: input.repoRoot,
|
|
10801
|
+
preflightCode: input.preflightCode,
|
|
10802
|
+
message: input.message,
|
|
10803
|
+
hint: input.hint,
|
|
10804
|
+
recommendedCommand
|
|
10805
|
+
});
|
|
10806
|
+
let outcome;
|
|
10807
|
+
const autoFixArgs = input.preflightCode ? AUTO_FIX_COMMAND[input.preflightCode] : void 0;
|
|
10808
|
+
if (!autoFixArgs) {
|
|
10809
|
+
outcome = {
|
|
10810
|
+
kind: "warn_only",
|
|
10811
|
+
reason: input.preflightCode ? "no_auto_fix_for_code" : "unknown_code"
|
|
10812
|
+
};
|
|
10813
|
+
} else {
|
|
10814
|
+
outcome = spawnFixDetached(input.repoRoot, autoFixArgs);
|
|
10815
|
+
marker.autoFix = mergeOutcomeIntoMarker(marker.autoFix, outcome);
|
|
10816
|
+
}
|
|
10817
|
+
try {
|
|
10818
|
+
await writeFinalizeFailureMarker(marker);
|
|
10819
|
+
} catch (writeErr) {
|
|
10820
|
+
await appendHookDiagnosticsEvent({
|
|
10821
|
+
hook: input.hook,
|
|
10822
|
+
sessionId: input.sessionId,
|
|
10823
|
+
turnId: input.turnId ?? void 0,
|
|
10824
|
+
stage: "finalize_failure_marker_write_failed",
|
|
10825
|
+
result: "error",
|
|
10826
|
+
reason: "exception",
|
|
10827
|
+
repoRoot: input.repoRoot,
|
|
10828
|
+
message: writeErr instanceof Error ? writeErr.message : String(writeErr)
|
|
10829
|
+
});
|
|
10830
|
+
}
|
|
10831
|
+
await appendHookDiagnosticsEvent({
|
|
10832
|
+
hook: input.hook,
|
|
10833
|
+
sessionId: input.sessionId,
|
|
10834
|
+
turnId: input.turnId ?? void 0,
|
|
10835
|
+
stage: "auto_fix_dispatched",
|
|
10836
|
+
result: outcome.kind === "spawned" ? "success" : outcome.kind === "warn_only" ? "info" : "error",
|
|
10837
|
+
reason: outcome.kind,
|
|
10838
|
+
repoRoot: input.repoRoot,
|
|
10839
|
+
fields: {
|
|
10840
|
+
preflightCode: input.preflightCode,
|
|
10841
|
+
command: "command" in outcome ? outcome.command : null,
|
|
10842
|
+
pid: outcome.kind === "spawned" ? outcome.pid ?? null : null,
|
|
10843
|
+
logPath: outcome.kind === "spawned" ? outcome.logPath : null,
|
|
10844
|
+
recommendedCommand
|
|
10845
|
+
},
|
|
10846
|
+
message: outcome.kind === "spawn_failed" ? outcome.message : null
|
|
10847
|
+
});
|
|
10848
|
+
return outcome;
|
|
10849
|
+
}
|
|
10850
|
+
function mergeOutcomeIntoMarker(existing, outcome) {
|
|
10851
|
+
if (outcome.kind === "spawned") {
|
|
10852
|
+
return {
|
|
10853
|
+
status: "in_progress",
|
|
10854
|
+
command: outcome.command,
|
|
10855
|
+
pid: outcome.pid ?? null,
|
|
10856
|
+
logPath: outcome.logPath,
|
|
10857
|
+
attemptedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10858
|
+
failureMessage: null
|
|
10859
|
+
};
|
|
10860
|
+
}
|
|
10861
|
+
if (outcome.kind === "spawn_throttled") {
|
|
10862
|
+
return {
|
|
10863
|
+
status: "in_progress",
|
|
10864
|
+
command: outcome.command,
|
|
10865
|
+
pid: existing.pid,
|
|
10866
|
+
logPath: existing.logPath,
|
|
10867
|
+
attemptedAt: existing.attemptedAt,
|
|
10868
|
+
failureMessage: null
|
|
10869
|
+
};
|
|
10870
|
+
}
|
|
10871
|
+
if (outcome.kind === "spawn_failed") {
|
|
10872
|
+
return {
|
|
10873
|
+
status: "spawn_failed",
|
|
10874
|
+
command: outcome.command,
|
|
10875
|
+
pid: null,
|
|
10876
|
+
logPath: null,
|
|
10877
|
+
attemptedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10878
|
+
failureMessage: outcome.message
|
|
10879
|
+
};
|
|
10880
|
+
}
|
|
10881
|
+
return existing;
|
|
10882
|
+
}
|
|
10883
|
+
|
|
10884
|
+
// node_modules/@remixhq/core/dist/chunk-RCNOSZP6.js
|
|
10236
10885
|
async function readJsonSafe(res) {
|
|
10237
10886
|
const ct = res.headers.get("content-type") ?? "";
|
|
10238
10887
|
if (!ct.toLowerCase().includes("application/json")) return null;
|
|
@@ -10245,8 +10894,13 @@ async function readJsonSafe(res) {
|
|
|
10245
10894
|
function createApiClient(config, opts) {
|
|
10246
10895
|
const apiKey = (opts?.apiKey ?? "").trim();
|
|
10247
10896
|
const tokenProvider = opts?.tokenProvider;
|
|
10897
|
+
const defaultTimeoutMs = typeof opts?.defaultRequestTimeoutMs === "number" && opts.defaultRequestTimeoutMs > 0 ? opts.defaultRequestTimeoutMs : null;
|
|
10248
10898
|
const CLIENT_KEY_HEADER = "x-comerge-api-key";
|
|
10249
|
-
|
|
10899
|
+
function makeTimeoutSignal(timeoutMs) {
|
|
10900
|
+
const ms = typeof timeoutMs === "number" && timeoutMs > 0 ? timeoutMs : defaultTimeoutMs;
|
|
10901
|
+
return ms != null ? AbortSignal.timeout(ms) : void 0;
|
|
10902
|
+
}
|
|
10903
|
+
async function request(path17, init, opts2) {
|
|
10250
10904
|
if (!tokenProvider) {
|
|
10251
10905
|
throw new RemixError("API client is missing a token provider.", {
|
|
10252
10906
|
exitCode: 1,
|
|
@@ -10254,9 +10908,10 @@ function createApiClient(config, opts) {
|
|
|
10254
10908
|
});
|
|
10255
10909
|
}
|
|
10256
10910
|
const auth = await tokenProvider();
|
|
10257
|
-
const url = new URL(
|
|
10911
|
+
const url = new URL(path17, config.apiUrl).toString();
|
|
10258
10912
|
const doFetch = async (bearer) => fetch(url, {
|
|
10259
10913
|
...init,
|
|
10914
|
+
signal: makeTimeoutSignal(opts2?.timeoutMs),
|
|
10260
10915
|
headers: {
|
|
10261
10916
|
Accept: "application/json",
|
|
10262
10917
|
"Content-Type": "application/json",
|
|
@@ -10273,12 +10928,16 @@ function createApiClient(config, opts) {
|
|
|
10273
10928
|
if (!res.ok) {
|
|
10274
10929
|
const body = await readJsonSafe(res);
|
|
10275
10930
|
const msg = (body && typeof body === "object" && body && "message" in body && typeof body.message === "string" ? body.message : null) ?? `Request failed (status ${res.status})`;
|
|
10276
|
-
throw new RemixError(msg, {
|
|
10931
|
+
throw new RemixError(msg, {
|
|
10932
|
+
exitCode: 1,
|
|
10933
|
+
hint: body ? JSON.stringify(body, null, 2) : null,
|
|
10934
|
+
statusCode: res.status
|
|
10935
|
+
});
|
|
10277
10936
|
}
|
|
10278
10937
|
const json = await readJsonSafe(res);
|
|
10279
10938
|
return json ?? null;
|
|
10280
10939
|
}
|
|
10281
|
-
async function requestBinary(
|
|
10940
|
+
async function requestBinary(path17, init, opts2) {
|
|
10282
10941
|
if (!tokenProvider) {
|
|
10283
10942
|
throw new RemixError("API client is missing a token provider.", {
|
|
10284
10943
|
exitCode: 1,
|
|
@@ -10286,9 +10945,10 @@ function createApiClient(config, opts) {
|
|
|
10286
10945
|
});
|
|
10287
10946
|
}
|
|
10288
10947
|
const auth = await tokenProvider();
|
|
10289
|
-
const url = new URL(
|
|
10948
|
+
const url = new URL(path17, config.apiUrl).toString();
|
|
10290
10949
|
const doFetch = async (bearer) => fetch(url, {
|
|
10291
10950
|
...init,
|
|
10951
|
+
signal: makeTimeoutSignal(opts2?.timeoutMs),
|
|
10292
10952
|
headers: {
|
|
10293
10953
|
Accept: "*/*",
|
|
10294
10954
|
...init?.headers ?? {},
|
|
@@ -10304,7 +10964,11 @@ function createApiClient(config, opts) {
|
|
|
10304
10964
|
if (!res.ok) {
|
|
10305
10965
|
const body = await readJsonSafe(res);
|
|
10306
10966
|
const msg = (body && typeof body === "object" && body && "message" in body && typeof body.message === "string" ? body.message : null) ?? `Request failed (status ${res.status})`;
|
|
10307
|
-
throw new RemixError(msg, {
|
|
10967
|
+
throw new RemixError(msg, {
|
|
10968
|
+
exitCode: 1,
|
|
10969
|
+
hint: body ? JSON.stringify(body, null, 2) : null,
|
|
10970
|
+
statusCode: res.status
|
|
10971
|
+
});
|
|
10308
10972
|
}
|
|
10309
10973
|
const contentDisposition = res.headers.get("content-disposition") ?? "";
|
|
10310
10974
|
const fileNameMatch = contentDisposition.match(/filename=\"([^\"]+)\"/i);
|
|
@@ -11136,8 +11800,8 @@ function getErrorMap() {
|
|
|
11136
11800
|
|
|
11137
11801
|
// node_modules/zod/v3/helpers/parseUtil.js
|
|
11138
11802
|
var makeIssue = (params) => {
|
|
11139
|
-
const { data, path:
|
|
11140
|
-
const fullPath = [...
|
|
11803
|
+
const { data, path: path17, errorMaps, issueData } = params;
|
|
11804
|
+
const fullPath = [...path17, ...issueData.path || []];
|
|
11141
11805
|
const fullIssue = {
|
|
11142
11806
|
...issueData,
|
|
11143
11807
|
path: fullPath
|
|
@@ -11253,11 +11917,11 @@ var errorUtil;
|
|
|
11253
11917
|
|
|
11254
11918
|
// node_modules/zod/v3/types.js
|
|
11255
11919
|
var ParseInputLazyPath = class {
|
|
11256
|
-
constructor(parent, value,
|
|
11920
|
+
constructor(parent, value, path17, key) {
|
|
11257
11921
|
this._cachedPath = [];
|
|
11258
11922
|
this.parent = parent;
|
|
11259
11923
|
this.data = value;
|
|
11260
|
-
this._path =
|
|
11924
|
+
this._path = path17;
|
|
11261
11925
|
this._key = key;
|
|
11262
11926
|
}
|
|
11263
11927
|
get path() {
|
|
@@ -14699,8 +15363,8 @@ var coerce = {
|
|
|
14699
15363
|
};
|
|
14700
15364
|
var NEVER = INVALID;
|
|
14701
15365
|
|
|
14702
|
-
// node_modules/@remixhq/core/dist/chunk-
|
|
14703
|
-
var
|
|
15366
|
+
// node_modules/@remixhq/core/dist/chunk-XETDXVGM.js
|
|
15367
|
+
var import_promises22 = __toESM(require("fs/promises"), 1);
|
|
14704
15368
|
var import_os3 = __toESM(require("os"), 1);
|
|
14705
15369
|
var import_path7 = __toESM(require("path"), 1);
|
|
14706
15370
|
|
|
@@ -15108,7 +15772,7 @@ var PostgrestError = class extends Error {
|
|
|
15108
15772
|
};
|
|
15109
15773
|
}
|
|
15110
15774
|
};
|
|
15111
|
-
function
|
|
15775
|
+
function sleep3(ms, signal) {
|
|
15112
15776
|
return new Promise((resolve) => {
|
|
15113
15777
|
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
15114
15778
|
resolve();
|
|
@@ -15304,7 +15968,7 @@ var PostgrestBuilder = class {
|
|
|
15304
15968
|
if (_this.retryEnabled && attemptCount < DEFAULT_MAX_RETRIES) {
|
|
15305
15969
|
const delay = getRetryDelay(attemptCount);
|
|
15306
15970
|
attemptCount++;
|
|
15307
|
-
await
|
|
15971
|
+
await sleep3(delay, _this.signal);
|
|
15308
15972
|
continue;
|
|
15309
15973
|
}
|
|
15310
15974
|
throw fetchError;
|
|
@@ -15315,7 +15979,7 @@ var PostgrestBuilder = class {
|
|
|
15315
15979
|
const delay = retryAfterHeader !== null ? Math.max(0, parseInt(retryAfterHeader, 10) || 0) * 1e3 : getRetryDelay(attemptCount);
|
|
15316
15980
|
await res$1.text();
|
|
15317
15981
|
attemptCount++;
|
|
15318
|
-
await
|
|
15982
|
+
await sleep3(delay, _this.signal);
|
|
15319
15983
|
continue;
|
|
15320
15984
|
}
|
|
15321
15985
|
return await _this.processResponse(res$1);
|
|
@@ -23805,8 +24469,8 @@ var IcebergError = class extends Error {
|
|
|
23805
24469
|
return this.status === 419;
|
|
23806
24470
|
}
|
|
23807
24471
|
};
|
|
23808
|
-
function buildUrl(baseUrl,
|
|
23809
|
-
const url = new URL(
|
|
24472
|
+
function buildUrl(baseUrl, path17, query) {
|
|
24473
|
+
const url = new URL(path17, baseUrl);
|
|
23810
24474
|
if (query) {
|
|
23811
24475
|
for (const [key, value] of Object.entries(query)) {
|
|
23812
24476
|
if (value !== void 0) {
|
|
@@ -23836,12 +24500,12 @@ function createFetchClient(options) {
|
|
|
23836
24500
|
return {
|
|
23837
24501
|
async request({
|
|
23838
24502
|
method,
|
|
23839
|
-
path:
|
|
24503
|
+
path: path17,
|
|
23840
24504
|
query,
|
|
23841
24505
|
body,
|
|
23842
24506
|
headers
|
|
23843
24507
|
}) {
|
|
23844
|
-
const url = buildUrl(options.baseUrl,
|
|
24508
|
+
const url = buildUrl(options.baseUrl, path17, query);
|
|
23845
24509
|
const authHeaders = await buildAuthHeaders(options.auth);
|
|
23846
24510
|
const res = await fetchFn(url, {
|
|
23847
24511
|
method,
|
|
@@ -24679,7 +25343,7 @@ var StorageFileApi = class extends BaseApiClient {
|
|
|
24679
25343
|
* @param path The relative file path. Should be of the format `folder/subfolder/filename.png`. The bucket must already exist before attempting to upload.
|
|
24680
25344
|
* @param fileBody The body of the file to be stored in the bucket.
|
|
24681
25345
|
*/
|
|
24682
|
-
async uploadOrUpdate(method,
|
|
25346
|
+
async uploadOrUpdate(method, path17, fileBody, fileOptions) {
|
|
24683
25347
|
var _this = this;
|
|
24684
25348
|
return _this.handleOperation(async () => {
|
|
24685
25349
|
let body;
|
|
@@ -24703,7 +25367,7 @@ var StorageFileApi = class extends BaseApiClient {
|
|
|
24703
25367
|
if ((typeof ReadableStream !== "undefined" && body instanceof ReadableStream || body && typeof body === "object" && "pipe" in body && typeof body.pipe === "function") && !options.duplex) options.duplex = "half";
|
|
24704
25368
|
}
|
|
24705
25369
|
if (fileOptions === null || fileOptions === void 0 ? void 0 : fileOptions.headers) for (const [key, value] of Object.entries(fileOptions.headers)) headers = setHeader(headers, key, value);
|
|
24706
|
-
const cleanPath = _this._removeEmptyFolders(
|
|
25370
|
+
const cleanPath = _this._removeEmptyFolders(path17);
|
|
24707
25371
|
const _path = _this._getFinalPath(cleanPath);
|
|
24708
25372
|
const data = await (method == "PUT" ? put : post)(_this.fetch, `${_this.url}/object/${_path}`, body, _objectSpread22({ headers }, (options === null || options === void 0 ? void 0 : options.duplex) ? { duplex: options.duplex } : {}));
|
|
24709
25373
|
return {
|
|
@@ -24764,8 +25428,8 @@ var StorageFileApi = class extends BaseApiClient {
|
|
|
24764
25428
|
* - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
|
|
24765
25429
|
* - For React Native, using either `Blob`, `File` or `FormData` does not work as intended. Upload file using `ArrayBuffer` from base64 file data instead, see example below.
|
|
24766
25430
|
*/
|
|
24767
|
-
async upload(
|
|
24768
|
-
return this.uploadOrUpdate("POST",
|
|
25431
|
+
async upload(path17, fileBody, fileOptions) {
|
|
25432
|
+
return this.uploadOrUpdate("POST", path17, fileBody, fileOptions);
|
|
24769
25433
|
}
|
|
24770
25434
|
/**
|
|
24771
25435
|
* Upload a file with a token generated from `createSignedUploadUrl`.
|
|
@@ -24804,9 +25468,9 @@ var StorageFileApi = class extends BaseApiClient {
|
|
|
24804
25468
|
* - `objects` table permissions: none
|
|
24805
25469
|
* - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
|
|
24806
25470
|
*/
|
|
24807
|
-
async uploadToSignedUrl(
|
|
25471
|
+
async uploadToSignedUrl(path17, token, fileBody, fileOptions) {
|
|
24808
25472
|
var _this3 = this;
|
|
24809
|
-
const cleanPath = _this3._removeEmptyFolders(
|
|
25473
|
+
const cleanPath = _this3._removeEmptyFolders(path17);
|
|
24810
25474
|
const _path = _this3._getFinalPath(cleanPath);
|
|
24811
25475
|
const url = new URL(_this3.url + `/object/upload/sign/${_path}`);
|
|
24812
25476
|
url.searchParams.set("token", token);
|
|
@@ -24868,10 +25532,10 @@ var StorageFileApi = class extends BaseApiClient {
|
|
|
24868
25532
|
* - `objects` table permissions: `insert`
|
|
24869
25533
|
* - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
|
|
24870
25534
|
*/
|
|
24871
|
-
async createSignedUploadUrl(
|
|
25535
|
+
async createSignedUploadUrl(path17, options) {
|
|
24872
25536
|
var _this4 = this;
|
|
24873
25537
|
return _this4.handleOperation(async () => {
|
|
24874
|
-
let _path = _this4._getFinalPath(
|
|
25538
|
+
let _path = _this4._getFinalPath(path17);
|
|
24875
25539
|
const headers = _objectSpread22({}, _this4.headers);
|
|
24876
25540
|
if (options === null || options === void 0 ? void 0 : options.upsert) headers["x-upsert"] = "true";
|
|
24877
25541
|
const data = await post(_this4.fetch, `${_this4.url}/object/upload/sign/${_path}`, {}, { headers });
|
|
@@ -24880,7 +25544,7 @@ var StorageFileApi = class extends BaseApiClient {
|
|
|
24880
25544
|
if (!token) throw new StorageError("No token returned by API");
|
|
24881
25545
|
return {
|
|
24882
25546
|
signedUrl: url.toString(),
|
|
24883
|
-
path:
|
|
25547
|
+
path: path17,
|
|
24884
25548
|
token
|
|
24885
25549
|
};
|
|
24886
25550
|
});
|
|
@@ -24936,8 +25600,8 @@ var StorageFileApi = class extends BaseApiClient {
|
|
|
24936
25600
|
* - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
|
|
24937
25601
|
* - For React Native, using either `Blob`, `File` or `FormData` does not work as intended. Update file using `ArrayBuffer` from base64 file data instead, see example below.
|
|
24938
25602
|
*/
|
|
24939
|
-
async update(
|
|
24940
|
-
return this.uploadOrUpdate("PUT",
|
|
25603
|
+
async update(path17, fileBody, fileOptions) {
|
|
25604
|
+
return this.uploadOrUpdate("PUT", path17, fileBody, fileOptions);
|
|
24941
25605
|
}
|
|
24942
25606
|
/**
|
|
24943
25607
|
* Moves an existing file to a new path in the same bucket.
|
|
@@ -25085,10 +25749,10 @@ var StorageFileApi = class extends BaseApiClient {
|
|
|
25085
25749
|
* - `objects` table permissions: `select`
|
|
25086
25750
|
* - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
|
|
25087
25751
|
*/
|
|
25088
|
-
async createSignedUrl(
|
|
25752
|
+
async createSignedUrl(path17, expiresIn, options) {
|
|
25089
25753
|
var _this8 = this;
|
|
25090
25754
|
return _this8.handleOperation(async () => {
|
|
25091
|
-
let _path = _this8._getFinalPath(
|
|
25755
|
+
let _path = _this8._getFinalPath(path17);
|
|
25092
25756
|
const hasTransform = typeof (options === null || options === void 0 ? void 0 : options.transform) === "object" && options.transform !== null && Object.keys(options.transform).length > 0;
|
|
25093
25757
|
let data = await post(_this8.fetch, `${_this8.url}/object/sign/${_path}`, _objectSpread22({ expiresIn }, hasTransform ? { transform: options.transform } : {}), { headers: _this8.headers });
|
|
25094
25758
|
const query = new URLSearchParams();
|
|
@@ -25222,13 +25886,13 @@ var StorageFileApi = class extends BaseApiClient {
|
|
|
25222
25886
|
* - `objects` table permissions: `select`
|
|
25223
25887
|
* - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
|
|
25224
25888
|
*/
|
|
25225
|
-
download(
|
|
25889
|
+
download(path17, options, parameters) {
|
|
25226
25890
|
const renderPath = typeof (options === null || options === void 0 ? void 0 : options.transform) === "object" && options.transform !== null && Object.keys(options.transform).length > 0 ? "render/image/authenticated" : "object";
|
|
25227
25891
|
const query = new URLSearchParams();
|
|
25228
25892
|
if (options === null || options === void 0 ? void 0 : options.transform) this.applyTransformOptsToQuery(query, options.transform);
|
|
25229
25893
|
if ((options === null || options === void 0 ? void 0 : options.cacheNonce) != null) query.set("cacheNonce", String(options.cacheNonce));
|
|
25230
25894
|
const queryString = query.toString();
|
|
25231
|
-
const _path = this._getFinalPath(
|
|
25895
|
+
const _path = this._getFinalPath(path17);
|
|
25232
25896
|
const downloadFn = () => get(this.fetch, `${this.url}/${renderPath}/${_path}${queryString ? `?${queryString}` : ""}`, {
|
|
25233
25897
|
headers: this.headers,
|
|
25234
25898
|
noResolveJson: true
|
|
@@ -25258,9 +25922,9 @@ var StorageFileApi = class extends BaseApiClient {
|
|
|
25258
25922
|
* }
|
|
25259
25923
|
* ```
|
|
25260
25924
|
*/
|
|
25261
|
-
async info(
|
|
25925
|
+
async info(path17) {
|
|
25262
25926
|
var _this10 = this;
|
|
25263
|
-
const _path = _this10._getFinalPath(
|
|
25927
|
+
const _path = _this10._getFinalPath(path17);
|
|
25264
25928
|
return _this10.handleOperation(async () => {
|
|
25265
25929
|
return recursiveToCamel(await get(_this10.fetch, `${_this10.url}/object/info/${_path}`, { headers: _this10.headers }));
|
|
25266
25930
|
});
|
|
@@ -25280,9 +25944,9 @@ var StorageFileApi = class extends BaseApiClient {
|
|
|
25280
25944
|
* .exists('folder/avatar1.png')
|
|
25281
25945
|
* ```
|
|
25282
25946
|
*/
|
|
25283
|
-
async exists(
|
|
25947
|
+
async exists(path17) {
|
|
25284
25948
|
var _this11 = this;
|
|
25285
|
-
const _path = _this11._getFinalPath(
|
|
25949
|
+
const _path = _this11._getFinalPath(path17);
|
|
25286
25950
|
try {
|
|
25287
25951
|
await head(_this11.fetch, `${_this11.url}/object/${_path}`, { headers: _this11.headers });
|
|
25288
25952
|
return {
|
|
@@ -25360,8 +26024,8 @@ var StorageFileApi = class extends BaseApiClient {
|
|
|
25360
26024
|
* - `objects` table permissions: none
|
|
25361
26025
|
* - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
|
|
25362
26026
|
*/
|
|
25363
|
-
getPublicUrl(
|
|
25364
|
-
const _path = this._getFinalPath(
|
|
26027
|
+
getPublicUrl(path17, options) {
|
|
26028
|
+
const _path = this._getFinalPath(path17);
|
|
25365
26029
|
const query = new URLSearchParams();
|
|
25366
26030
|
if (options === null || options === void 0 ? void 0 : options.download) query.set("download", options.download === true ? "" : options.download);
|
|
25367
26031
|
if (options === null || options === void 0 ? void 0 : options.transform) this.applyTransformOptsToQuery(query, options.transform);
|
|
@@ -25498,10 +26162,10 @@ var StorageFileApi = class extends BaseApiClient {
|
|
|
25498
26162
|
* - `objects` table permissions: `select`
|
|
25499
26163
|
* - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
|
|
25500
26164
|
*/
|
|
25501
|
-
async list(
|
|
26165
|
+
async list(path17, options, parameters) {
|
|
25502
26166
|
var _this13 = this;
|
|
25503
26167
|
return _this13.handleOperation(async () => {
|
|
25504
|
-
const body = _objectSpread22(_objectSpread22(_objectSpread22({}, DEFAULT_SEARCH_OPTIONS), options), {}, { prefix:
|
|
26168
|
+
const body = _objectSpread22(_objectSpread22(_objectSpread22({}, DEFAULT_SEARCH_OPTIONS), options), {}, { prefix: path17 || "" });
|
|
25505
26169
|
return await post(_this13.fetch, `${_this13.url}/object/list/${_this13.bucketId}`, body, { headers: _this13.headers }, parameters);
|
|
25506
26170
|
});
|
|
25507
26171
|
}
|
|
@@ -25565,11 +26229,11 @@ var StorageFileApi = class extends BaseApiClient {
|
|
|
25565
26229
|
if (typeof Buffer !== "undefined") return Buffer.from(data).toString("base64");
|
|
25566
26230
|
return btoa(data);
|
|
25567
26231
|
}
|
|
25568
|
-
_getFinalPath(
|
|
25569
|
-
return `${this.bucketId}/${
|
|
26232
|
+
_getFinalPath(path17) {
|
|
26233
|
+
return `${this.bucketId}/${path17.replace(/^\/+/, "")}`;
|
|
25570
26234
|
}
|
|
25571
|
-
_removeEmptyFolders(
|
|
25572
|
-
return
|
|
26235
|
+
_removeEmptyFolders(path17) {
|
|
26236
|
+
return path17.replace(/^\/|\/$/g, "").replace(/\/+/g, "/");
|
|
25573
26237
|
}
|
|
25574
26238
|
/** Modifies the `query`, appending values the from `transform` */
|
|
25575
26239
|
applyTransformOptsToQuery(query, transform) {
|
|
@@ -27312,7 +27976,7 @@ function decodeJWT(token) {
|
|
|
27312
27976
|
};
|
|
27313
27977
|
return data;
|
|
27314
27978
|
}
|
|
27315
|
-
async function
|
|
27979
|
+
async function sleep4(time) {
|
|
27316
27980
|
return await new Promise((accept) => {
|
|
27317
27981
|
setTimeout(() => accept(null), time);
|
|
27318
27982
|
});
|
|
@@ -33105,7 +33769,7 @@ var GoTrueClient = class _GoTrueClient {
|
|
|
33105
33769
|
const startedAt = Date.now();
|
|
33106
33770
|
return await retryable(async (attempt) => {
|
|
33107
33771
|
if (attempt > 0) {
|
|
33108
|
-
await
|
|
33772
|
+
await sleep4(200 * Math.pow(2, attempt - 1));
|
|
33109
33773
|
}
|
|
33110
33774
|
this._debug(debugName, "refreshing attempt", attempt);
|
|
33111
33775
|
return await _request(this.fetch, "POST", `${this.url}/token?grant_type=refresh_token`, {
|
|
@@ -34644,7 +35308,7 @@ function shouldShowDeprecationWarning() {
|
|
|
34644
35308
|
}
|
|
34645
35309
|
if (shouldShowDeprecationWarning()) console.warn("\u26A0\uFE0F Node.js 18 and below are deprecated and will no longer be supported in future versions of @supabase/supabase-js. Please upgrade to Node.js 20 or later. For more information, visit: https://github.com/orgs/supabase/discussions/37217");
|
|
34646
35310
|
|
|
34647
|
-
// node_modules/@remixhq/core/dist/chunk-
|
|
35311
|
+
// node_modules/@remixhq/core/dist/chunk-XETDXVGM.js
|
|
34648
35312
|
var storedSessionSchema = external_exports.object({
|
|
34649
35313
|
access_token: external_exports.string().min(1),
|
|
34650
35314
|
refresh_token: external_exports.string().min(1),
|
|
@@ -34677,24 +35341,24 @@ async function maybeLoadKeytar() {
|
|
|
34677
35341
|
}
|
|
34678
35342
|
async function ensurePathPermissions(filePath) {
|
|
34679
35343
|
const dir = import_path7.default.dirname(filePath);
|
|
34680
|
-
await
|
|
35344
|
+
await import_promises22.default.mkdir(dir, { recursive: true });
|
|
34681
35345
|
try {
|
|
34682
|
-
await
|
|
35346
|
+
await import_promises22.default.chmod(dir, 448);
|
|
34683
35347
|
} catch {
|
|
34684
35348
|
}
|
|
34685
35349
|
try {
|
|
34686
|
-
await
|
|
35350
|
+
await import_promises22.default.chmod(filePath, 384);
|
|
34687
35351
|
} catch {
|
|
34688
35352
|
}
|
|
34689
35353
|
}
|
|
34690
|
-
async function
|
|
34691
|
-
await
|
|
35354
|
+
async function writeJsonAtomic3(filePath, value) {
|
|
35355
|
+
await import_promises22.default.mkdir(import_path7.default.dirname(filePath), { recursive: true });
|
|
34692
35356
|
const tmpPath = `${filePath}.tmp-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
34693
|
-
await
|
|
34694
|
-
await
|
|
35357
|
+
await import_promises22.default.writeFile(tmpPath, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
35358
|
+
await import_promises22.default.rename(tmpPath, filePath);
|
|
34695
35359
|
}
|
|
34696
35360
|
async function writeSessionFileFallback(filePath, session) {
|
|
34697
|
-
await
|
|
35361
|
+
await writeJsonAtomic3(filePath, session);
|
|
34698
35362
|
await ensurePathPermissions(filePath);
|
|
34699
35363
|
}
|
|
34700
35364
|
function createLocalSessionStore(params) {
|
|
@@ -34714,7 +35378,7 @@ function createLocalSessionStore(params) {
|
|
|
34714
35378
|
}
|
|
34715
35379
|
}
|
|
34716
35380
|
async function readFile() {
|
|
34717
|
-
const raw = await
|
|
35381
|
+
const raw = await import_promises22.default.readFile(filePath, "utf8").catch(() => null);
|
|
34718
35382
|
if (!raw) return null;
|
|
34719
35383
|
try {
|
|
34720
35384
|
const parsed = storedSessionSchema.safeParse(JSON.parse(raw));
|
|
@@ -34858,7 +35522,7 @@ function createSupabaseAuthHelpers(config) {
|
|
|
34858
35522
|
};
|
|
34859
35523
|
}
|
|
34860
35524
|
|
|
34861
|
-
// node_modules/@remixhq/core/dist/chunk-
|
|
35525
|
+
// node_modules/@remixhq/core/dist/chunk-XCZRNB35.js
|
|
34862
35526
|
var DEFAULT_API_URL = "https://api.remix.one";
|
|
34863
35527
|
var DEFAULT_SUPABASE_URL = "https://xtfxwbckjpfmqubnsusu.supabase.co";
|
|
34864
35528
|
var DEFAULT_SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inh0Znh3YmNranBmbXF1Ym5zdXN1Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjA2MDEyMzAsImV4cCI6MjA3NjE3NzIzMH0.dzWGAWrK4CvrmHVHzf8w7JlUZohdap0ZPnLZnABMV8s";
|
|
@@ -34896,6 +35560,7 @@ async function resolveConfig(_opts) {
|
|
|
34896
35560
|
}
|
|
34897
35561
|
|
|
34898
35562
|
// src/hook-auth.ts
|
|
35563
|
+
var HOOK_API_REQUEST_TIMEOUT_MS = 6e4;
|
|
34899
35564
|
async function createHookCollabApiClient() {
|
|
34900
35565
|
const config = await resolveConfig();
|
|
34901
35566
|
const sessionStore = createLocalSessionStore();
|
|
@@ -34908,307 +35573,11 @@ async function createHookCollabApiClient() {
|
|
|
34908
35573
|
}
|
|
34909
35574
|
});
|
|
34910
35575
|
return createApiClient(config, {
|
|
34911
|
-
tokenProvider
|
|
35576
|
+
tokenProvider,
|
|
35577
|
+
defaultRequestTimeoutMs: HOOK_API_REQUEST_TIMEOUT_MS
|
|
34912
35578
|
});
|
|
34913
35579
|
}
|
|
34914
35580
|
|
|
34915
|
-
// src/hook-diagnostics.ts
|
|
34916
|
-
var import_node_crypto2 = require("crypto");
|
|
34917
|
-
var import_promises21 = __toESM(require("fs/promises"), 1);
|
|
34918
|
-
var import_node_os6 = __toESM(require("os"), 1);
|
|
34919
|
-
var import_node_path8 = __toESM(require("path"), 1);
|
|
34920
|
-
|
|
34921
|
-
// src/hook-state.ts
|
|
34922
|
-
var import_promises20 = __toESM(require("fs/promises"), 1);
|
|
34923
|
-
var import_node_os5 = __toESM(require("os"), 1);
|
|
34924
|
-
var import_node_path7 = __toESM(require("path"), 1);
|
|
34925
|
-
var import_node_crypto = require("crypto");
|
|
34926
|
-
function stateRoot2() {
|
|
34927
|
-
const configured = process.env.REMIX_CLAUDE_PLUGIN_HOOK_STATE_ROOT?.trim();
|
|
34928
|
-
return configured || import_node_path7.default.join(import_node_os5.default.tmpdir(), "remix-claude-plugin-hooks");
|
|
34929
|
-
}
|
|
34930
|
-
function statePath(sessionId) {
|
|
34931
|
-
return import_node_path7.default.join(stateRoot2(), `${sessionId}.json`);
|
|
34932
|
-
}
|
|
34933
|
-
function stateLockPath(sessionId) {
|
|
34934
|
-
return import_node_path7.default.join(stateRoot2(), `${sessionId}.lock`);
|
|
34935
|
-
}
|
|
34936
|
-
function stateLockMetaPath(sessionId) {
|
|
34937
|
-
return import_node_path7.default.join(stateLockPath(sessionId), "owner.json");
|
|
34938
|
-
}
|
|
34939
|
-
async function writeJsonAtomic3(filePath, value) {
|
|
34940
|
-
await import_promises20.default.mkdir(import_node_path7.default.dirname(filePath), { recursive: true });
|
|
34941
|
-
const tmpPath = `${filePath}.tmp-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
34942
|
-
await import_promises20.default.writeFile(tmpPath, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
34943
|
-
await import_promises20.default.rename(tmpPath, filePath);
|
|
34944
|
-
}
|
|
34945
|
-
var STATE_LOCK_WAIT_MS = 2e3;
|
|
34946
|
-
var STATE_LOCK_POLL_MS = 25;
|
|
34947
|
-
var STATE_LOCK_STALE_MS = 3e4;
|
|
34948
|
-
var STATE_LOCK_HEARTBEAT_MS = 5e3;
|
|
34949
|
-
async function sleep4(ms) {
|
|
34950
|
-
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
34951
|
-
}
|
|
34952
|
-
async function readStateLockMetadata(sessionId) {
|
|
34953
|
-
const raw = await import_promises20.default.readFile(stateLockMetaPath(sessionId), "utf8").catch(() => null);
|
|
34954
|
-
if (!raw) return null;
|
|
34955
|
-
try {
|
|
34956
|
-
const parsed = JSON.parse(raw);
|
|
34957
|
-
if (typeof parsed.ownerId !== "string" || typeof parsed.pid !== "number" || typeof parsed.createdAt !== "string" || typeof parsed.heartbeatAt !== "string") {
|
|
34958
|
-
return null;
|
|
34959
|
-
}
|
|
34960
|
-
return {
|
|
34961
|
-
ownerId: parsed.ownerId,
|
|
34962
|
-
pid: parsed.pid,
|
|
34963
|
-
createdAt: parsed.createdAt,
|
|
34964
|
-
heartbeatAt: parsed.heartbeatAt
|
|
34965
|
-
};
|
|
34966
|
-
} catch {
|
|
34967
|
-
return null;
|
|
34968
|
-
}
|
|
34969
|
-
}
|
|
34970
|
-
async function writeStateLockMetadata(sessionId, metadata) {
|
|
34971
|
-
await writeJsonAtomic3(stateLockMetaPath(sessionId), metadata);
|
|
34972
|
-
}
|
|
34973
|
-
async function tryRemoveStaleStateLock(sessionId) {
|
|
34974
|
-
const lockPath = stateLockPath(sessionId);
|
|
34975
|
-
const metadata = await readStateLockMetadata(sessionId);
|
|
34976
|
-
const staleByHeartbeat = metadata && Date.now() - new Date(metadata.heartbeatAt).getTime() > STATE_LOCK_STALE_MS;
|
|
34977
|
-
if (staleByHeartbeat) {
|
|
34978
|
-
await import_promises20.default.rm(lockPath, { recursive: true, force: true }).catch(() => void 0);
|
|
34979
|
-
return true;
|
|
34980
|
-
}
|
|
34981
|
-
if (!metadata) {
|
|
34982
|
-
const lockStat = await import_promises20.default.stat(lockPath).catch(() => null);
|
|
34983
|
-
if (lockStat && Date.now() - lockStat.mtimeMs > STATE_LOCK_STALE_MS) {
|
|
34984
|
-
await import_promises20.default.rm(lockPath, { recursive: true, force: true }).catch(() => void 0);
|
|
34985
|
-
return true;
|
|
34986
|
-
}
|
|
34987
|
-
}
|
|
34988
|
-
return false;
|
|
34989
|
-
}
|
|
34990
|
-
async function acquireStateLock(sessionId) {
|
|
34991
|
-
const lockPath = stateLockPath(sessionId);
|
|
34992
|
-
const deadline = Date.now() + STATE_LOCK_WAIT_MS;
|
|
34993
|
-
await import_promises20.default.mkdir(stateRoot2(), { recursive: true });
|
|
34994
|
-
while (true) {
|
|
34995
|
-
try {
|
|
34996
|
-
await import_promises20.default.mkdir(lockPath);
|
|
34997
|
-
const ownerId = (0, import_node_crypto.randomUUID)();
|
|
34998
|
-
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
34999
|
-
const metadata = {
|
|
35000
|
-
ownerId,
|
|
35001
|
-
pid: process.pid,
|
|
35002
|
-
createdAt,
|
|
35003
|
-
heartbeatAt: createdAt
|
|
35004
|
-
};
|
|
35005
|
-
await writeStateLockMetadata(sessionId, metadata);
|
|
35006
|
-
let released = false;
|
|
35007
|
-
const heartbeat = setInterval(() => {
|
|
35008
|
-
if (released) return;
|
|
35009
|
-
void writeStateLockMetadata(sessionId, {
|
|
35010
|
-
...metadata,
|
|
35011
|
-
heartbeatAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
35012
|
-
}).catch(() => void 0);
|
|
35013
|
-
}, STATE_LOCK_HEARTBEAT_MS);
|
|
35014
|
-
heartbeat.unref?.();
|
|
35015
|
-
return async () => {
|
|
35016
|
-
if (released) return;
|
|
35017
|
-
released = true;
|
|
35018
|
-
clearInterval(heartbeat);
|
|
35019
|
-
const currentMetadata = await readStateLockMetadata(sessionId);
|
|
35020
|
-
if (currentMetadata?.ownerId === ownerId) {
|
|
35021
|
-
await import_promises20.default.rm(lockPath, { recursive: true, force: true }).catch(() => void 0);
|
|
35022
|
-
}
|
|
35023
|
-
};
|
|
35024
|
-
} catch (error) {
|
|
35025
|
-
const code = error && typeof error === "object" && "code" in error ? error.code : null;
|
|
35026
|
-
if (code !== "EEXIST") {
|
|
35027
|
-
throw error;
|
|
35028
|
-
}
|
|
35029
|
-
if (await tryRemoveStaleStateLock(sessionId)) {
|
|
35030
|
-
continue;
|
|
35031
|
-
}
|
|
35032
|
-
if (Date.now() >= deadline) {
|
|
35033
|
-
throw new Error(`Timed out acquiring hook state lock for session ${sessionId}.`);
|
|
35034
|
-
}
|
|
35035
|
-
await sleep4(STATE_LOCK_POLL_MS);
|
|
35036
|
-
}
|
|
35037
|
-
}
|
|
35038
|
-
}
|
|
35039
|
-
async function withStateLock(sessionId, fn) {
|
|
35040
|
-
const release = await acquireStateLock(sessionId);
|
|
35041
|
-
try {
|
|
35042
|
-
return await fn();
|
|
35043
|
-
} finally {
|
|
35044
|
-
await release();
|
|
35045
|
-
}
|
|
35046
|
-
}
|
|
35047
|
-
async function savePendingTurnState(state) {
|
|
35048
|
-
await writeJsonAtomic3(statePath(state.sessionId), state);
|
|
35049
|
-
}
|
|
35050
|
-
async function createPendingTurnState(params) {
|
|
35051
|
-
return withStateLock(params.sessionId, async () => {
|
|
35052
|
-
const state = {
|
|
35053
|
-
sessionId: params.sessionId,
|
|
35054
|
-
turnId: (0, import_node_crypto.randomUUID)(),
|
|
35055
|
-
prompt: params.prompt,
|
|
35056
|
-
initialCwd: params.initialCwd?.trim() || null,
|
|
35057
|
-
intent: params.intent,
|
|
35058
|
-
submittedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
35059
|
-
consultedMemory: false,
|
|
35060
|
-
touchedRepos: {},
|
|
35061
|
-
turnFailureMessage: null,
|
|
35062
|
-
turnFailureHint: null,
|
|
35063
|
-
turnFailedAt: null
|
|
35064
|
-
};
|
|
35065
|
-
await savePendingTurnState(state);
|
|
35066
|
-
return state;
|
|
35067
|
-
});
|
|
35068
|
-
}
|
|
35069
|
-
|
|
35070
|
-
// package.json
|
|
35071
|
-
var package_default = {
|
|
35072
|
-
name: "@remixhq/claude-plugin",
|
|
35073
|
-
version: "0.1.22",
|
|
35074
|
-
description: "Claude Code plugin for Remix collaboration workflows",
|
|
35075
|
-
homepage: "https://github.com/RemixDotOne/remix-claude-plugin",
|
|
35076
|
-
license: "MIT",
|
|
35077
|
-
repository: {
|
|
35078
|
-
type: "git",
|
|
35079
|
-
url: "https://github.com/RemixDotOne/remix-claude-plugin.git"
|
|
35080
|
-
},
|
|
35081
|
-
type: "module",
|
|
35082
|
-
engines: {
|
|
35083
|
-
node: ">=20"
|
|
35084
|
-
},
|
|
35085
|
-
publishConfig: {
|
|
35086
|
-
access: "public"
|
|
35087
|
-
},
|
|
35088
|
-
files: [
|
|
35089
|
-
"dist",
|
|
35090
|
-
".claude-plugin/plugin.json",
|
|
35091
|
-
".mcp.json",
|
|
35092
|
-
"skills",
|
|
35093
|
-
"hooks",
|
|
35094
|
-
"agents"
|
|
35095
|
-
],
|
|
35096
|
-
exports: {
|
|
35097
|
-
".": {
|
|
35098
|
-
types: "./dist/index.d.ts",
|
|
35099
|
-
import: "./dist/index.js"
|
|
35100
|
-
}
|
|
35101
|
-
},
|
|
35102
|
-
scripts: {
|
|
35103
|
-
build: "tsup",
|
|
35104
|
-
postbuild: `node -e "const fs=require('node:fs'); for (const p of ['dist/mcp-server.cjs','dist/hook-pre-git.cjs','dist/hook-user-prompt.cjs','dist/hook-post-collab.cjs','dist/hook-stop-collab.cjs']) fs.chmodSync(p, 0o755);"`,
|
|
35105
|
-
dev: "tsx src/mcp-server.ts",
|
|
35106
|
-
typecheck: "tsc -p tsconfig.json --noEmit",
|
|
35107
|
-
test: "node --import tsx --test 'src/**/*.test.ts'",
|
|
35108
|
-
prepack: "npm run build"
|
|
35109
|
-
},
|
|
35110
|
-
dependencies: {
|
|
35111
|
-
"@remixhq/core": "^0.1.17",
|
|
35112
|
-
"@remixhq/mcp": "^0.1.17"
|
|
35113
|
-
},
|
|
35114
|
-
devDependencies: {
|
|
35115
|
-
"@types/node": "^25.4.0",
|
|
35116
|
-
tsup: "^8.5.1",
|
|
35117
|
-
tsx: "^4.21.0",
|
|
35118
|
-
typescript: "^5.9.3"
|
|
35119
|
-
}
|
|
35120
|
-
};
|
|
35121
|
-
|
|
35122
|
-
// src/metadata.ts
|
|
35123
|
-
var pluginMetadata = {
|
|
35124
|
-
name: package_default.name,
|
|
35125
|
-
version: package_default.version,
|
|
35126
|
-
description: package_default.description,
|
|
35127
|
-
pluginId: "remix",
|
|
35128
|
-
agentName: "remix-collab"
|
|
35129
|
-
};
|
|
35130
|
-
|
|
35131
|
-
// src/hook-diagnostics.ts
|
|
35132
|
-
var MAX_LOG_BYTES = 512 * 1024;
|
|
35133
|
-
function resolveClaudeRoot() {
|
|
35134
|
-
const configured = process.env.CLAUDE_CONFIG_DIR?.trim();
|
|
35135
|
-
return configured || import_node_path8.default.join(import_node_os6.default.homedir(), ".claude");
|
|
35136
|
-
}
|
|
35137
|
-
function resolvePluginDataDirName() {
|
|
35138
|
-
return `${pluginMetadata.pluginId}-${pluginMetadata.pluginId}`;
|
|
35139
|
-
}
|
|
35140
|
-
function getHookDiagnosticsDirPath() {
|
|
35141
|
-
const configured = process.env.REMIX_CLAUDE_PLUGIN_HOOK_DIAGNOSTICS_DIR?.trim();
|
|
35142
|
-
return configured || import_node_path8.default.join(resolveClaudeRoot(), "plugins", "data", resolvePluginDataDirName());
|
|
35143
|
-
}
|
|
35144
|
-
function getHookDiagnosticsLogPath() {
|
|
35145
|
-
return import_node_path8.default.join(getHookDiagnosticsDirPath(), "hooks.ndjson");
|
|
35146
|
-
}
|
|
35147
|
-
function toFieldValue(value) {
|
|
35148
|
-
if (value === null) return null;
|
|
35149
|
-
if (typeof value === "string") return value;
|
|
35150
|
-
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
35151
|
-
if (typeof value === "boolean") return value;
|
|
35152
|
-
return void 0;
|
|
35153
|
-
}
|
|
35154
|
-
function normalizeFields(fields) {
|
|
35155
|
-
if (!fields) return {};
|
|
35156
|
-
const normalizedEntries = Object.entries(fields).map(([key, value]) => {
|
|
35157
|
-
const normalized = toFieldValue(value);
|
|
35158
|
-
return normalized === void 0 ? null : [key, normalized];
|
|
35159
|
-
}).filter((entry) => entry !== null);
|
|
35160
|
-
return Object.fromEntries(normalizedEntries);
|
|
35161
|
-
}
|
|
35162
|
-
async function rotateLogIfNeeded(logPath) {
|
|
35163
|
-
const stat = await import_promises21.default.stat(logPath).catch(() => null);
|
|
35164
|
-
if (!stat || stat.size < MAX_LOG_BYTES) {
|
|
35165
|
-
return;
|
|
35166
|
-
}
|
|
35167
|
-
const rotatedPath = `${logPath}.1`;
|
|
35168
|
-
await import_promises21.default.rm(rotatedPath, { force: true }).catch(() => void 0);
|
|
35169
|
-
await import_promises21.default.rename(logPath, rotatedPath).catch(() => void 0);
|
|
35170
|
-
}
|
|
35171
|
-
function summarizeText(value) {
|
|
35172
|
-
if (typeof value !== "string" || !value.trim()) {
|
|
35173
|
-
return {
|
|
35174
|
-
present: false,
|
|
35175
|
-
length: 0,
|
|
35176
|
-
sha256Prefix: null
|
|
35177
|
-
};
|
|
35178
|
-
}
|
|
35179
|
-
const trimmed = value.trim();
|
|
35180
|
-
return {
|
|
35181
|
-
present: true,
|
|
35182
|
-
length: trimmed.length,
|
|
35183
|
-
sha256Prefix: (0, import_node_crypto2.createHash)("sha256").update(trimmed).digest("hex").slice(0, 12)
|
|
35184
|
-
};
|
|
35185
|
-
}
|
|
35186
|
-
async function appendHookDiagnosticsEvent(params) {
|
|
35187
|
-
try {
|
|
35188
|
-
const logPath = getHookDiagnosticsLogPath();
|
|
35189
|
-
await import_promises21.default.mkdir(import_node_path8.default.dirname(logPath), { recursive: true });
|
|
35190
|
-
await rotateLogIfNeeded(logPath);
|
|
35191
|
-
const event = {
|
|
35192
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
35193
|
-
hook: params.hook,
|
|
35194
|
-
pluginVersion: pluginMetadata.version,
|
|
35195
|
-
pid: process.pid,
|
|
35196
|
-
sessionId: params.sessionId?.trim() || null,
|
|
35197
|
-
turnId: params.turnId?.trim() || null,
|
|
35198
|
-
stage: params.stage.trim(),
|
|
35199
|
-
result: params.result,
|
|
35200
|
-
reason: params.reason?.trim() || null,
|
|
35201
|
-
toolName: params.toolName?.trim() || null,
|
|
35202
|
-
repoRoot: params.repoRoot?.trim() || null,
|
|
35203
|
-
message: params.message?.trim() || null,
|
|
35204
|
-
fields: normalizeFields(params.fields)
|
|
35205
|
-
};
|
|
35206
|
-
await import_promises21.default.appendFile(logPath, `${JSON.stringify(event)}
|
|
35207
|
-
`, "utf8");
|
|
35208
|
-
} catch {
|
|
35209
|
-
}
|
|
35210
|
-
}
|
|
35211
|
-
|
|
35212
35581
|
// src/deferred-turn-drainer.ts
|
|
35213
35582
|
var collabFinalizeTurn2 = collabFinalizeTurn;
|
|
35214
35583
|
var drainPendingFinalizeQueue2 = drainPendingFinalizeQueue;
|
|
@@ -35218,6 +35587,16 @@ var HOOK_ACTOR = {
|
|
|
35218
35587
|
version: pluginMetadata.version,
|
|
35219
35588
|
provider: "anthropic"
|
|
35220
35589
|
};
|
|
35590
|
+
function getDrainerErrorDetails(error) {
|
|
35591
|
+
if (error instanceof Error) {
|
|
35592
|
+
const hint = typeof error.hint === "string" ? String(error.hint) : null;
|
|
35593
|
+
const codeRaw = error.code;
|
|
35594
|
+
const preflightCode = isFinalizePreflightFailureCode(codeRaw) ? codeRaw : null;
|
|
35595
|
+
return { message: error.message || "Deferred turn recording failed.", hint, preflightCode };
|
|
35596
|
+
}
|
|
35597
|
+
const message = typeof error === "string" && error.trim() ? error.trim() : "Deferred turn recording failed.";
|
|
35598
|
+
return { message, hint: null, preflightCode: null };
|
|
35599
|
+
}
|
|
35221
35600
|
var DEFERRED_TURN_DRAIN_POLL_INTERVAL_MS = 3e3;
|
|
35222
35601
|
var DEFERRED_TURN_DRAIN_MAX_WAIT_MS = 15 * 60 * 1e3;
|
|
35223
35602
|
var DEFERRED_TURN_DRAIN_LOCK_HEARTBEAT_MS = 3e4;
|
|
@@ -35236,10 +35615,10 @@ function repoLockFileName(repoRoot) {
|
|
|
35236
35615
|
return `.drainer-${hash}.lock`;
|
|
35237
35616
|
}
|
|
35238
35617
|
function repoLockPath(repoRoot) {
|
|
35239
|
-
return
|
|
35618
|
+
return import_node_path11.default.join(getDeferredTurnDirPath(), repoLockFileName(repoRoot));
|
|
35240
35619
|
}
|
|
35241
35620
|
async function readDrainLockMetadata(lockPath) {
|
|
35242
|
-
const raw = await
|
|
35621
|
+
const raw = await import_promises23.default.readFile(lockPath, "utf8").catch(() => null);
|
|
35243
35622
|
if (!raw) return null;
|
|
35244
35623
|
try {
|
|
35245
35624
|
const parsed = JSON.parse(raw);
|
|
@@ -35253,15 +35632,15 @@ async function readDrainLockMetadata(lockPath) {
|
|
|
35253
35632
|
}
|
|
35254
35633
|
async function writeDrainLockMetadata(lockPath, metadata) {
|
|
35255
35634
|
const tmpPath = `${lockPath}.tmp-${process.pid}-${Date.now()}`;
|
|
35256
|
-
await
|
|
35257
|
-
await
|
|
35635
|
+
await import_promises23.default.writeFile(tmpPath, JSON.stringify(metadata), "utf8");
|
|
35636
|
+
await import_promises23.default.rename(tmpPath, lockPath);
|
|
35258
35637
|
}
|
|
35259
35638
|
async function tryAcquireDrainLock(repoRoot) {
|
|
35260
35639
|
const lockPath = repoLockPath(repoRoot);
|
|
35261
|
-
await
|
|
35640
|
+
await import_promises23.default.mkdir(import_node_path11.default.dirname(lockPath), { recursive: true });
|
|
35262
35641
|
const existingMeta = await readDrainLockMetadata(lockPath);
|
|
35263
35642
|
if (existingMeta) {
|
|
35264
|
-
const lockStat = await
|
|
35643
|
+
const lockStat = await import_promises23.default.stat(lockPath).catch(() => null);
|
|
35265
35644
|
const ageMs = lockStat ? Date.now() - lockStat.mtimeMs : Number.POSITIVE_INFINITY;
|
|
35266
35645
|
const fresh = ageMs <= DEFERRED_TURN_DRAIN_LOCK_STALE_MS;
|
|
35267
35646
|
const alive = isPidAlive(existingMeta.pid);
|
|
@@ -35279,11 +35658,11 @@ async function tryAcquireDrainLock(repoRoot) {
|
|
|
35279
35658
|
async function releaseDrainLock(lockPath) {
|
|
35280
35659
|
const meta = await readDrainLockMetadata(lockPath);
|
|
35281
35660
|
if (meta && meta.pid !== process.pid) return;
|
|
35282
|
-
await
|
|
35661
|
+
await import_promises23.default.rm(lockPath, { force: true }).catch(() => void 0);
|
|
35283
35662
|
}
|
|
35284
35663
|
async function heartbeatDrainLock(lockPath) {
|
|
35285
35664
|
const now = /* @__PURE__ */ new Date();
|
|
35286
|
-
await
|
|
35665
|
+
await import_promises23.default.utimes(lockPath, now, now).catch(() => void 0);
|
|
35287
35666
|
}
|
|
35288
35667
|
async function sleep5(ms) {
|
|
35289
35668
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -35383,6 +35762,7 @@ async function runStandaloneDeferredTurnDrainer(repoRoot) {
|
|
|
35383
35762
|
let api = null;
|
|
35384
35763
|
let recordedTotal = 0;
|
|
35385
35764
|
let failedTotal = 0;
|
|
35765
|
+
let droppedTotal = 0;
|
|
35386
35766
|
let exitReason = "queue_empty";
|
|
35387
35767
|
try {
|
|
35388
35768
|
while (true) {
|
|
@@ -35413,7 +35793,49 @@ async function runStandaloneDeferredTurnDrainer(repoRoot) {
|
|
|
35413
35793
|
const bindingState = await readCollabBindingState(repoRoot).catch(() => null);
|
|
35414
35794
|
const currentBranch = bindingState?.currentBranch ?? null;
|
|
35415
35795
|
const isCurrentBranchBound = bindingState?.binding != null;
|
|
35416
|
-
const
|
|
35796
|
+
const currentAppId = bindingState?.binding?.currentAppId ?? null;
|
|
35797
|
+
const currentProjectId = bindingState?.binding?.projectId ?? bindingState?.projectId ?? null;
|
|
35798
|
+
let droppedThisPass = 0;
|
|
35799
|
+
const liveEntries = [];
|
|
35800
|
+
for (const entry of entries) {
|
|
35801
|
+
const appIdMismatch = entry.record.appIdAtDefer != null && currentAppId != null && entry.record.appIdAtDefer !== currentAppId;
|
|
35802
|
+
const projectIdMismatch = entry.record.projectIdAtDefer != null && currentProjectId != null && entry.record.projectIdAtDefer !== currentProjectId;
|
|
35803
|
+
if (appIdMismatch || projectIdMismatch) {
|
|
35804
|
+
await deleteDeferredTurnFile(entry.filePath);
|
|
35805
|
+
droppedThisPass += 1;
|
|
35806
|
+
await appendHookDiagnosticsEvent({
|
|
35807
|
+
hook: "deferredTurnDrainer",
|
|
35808
|
+
sessionId: sessionMarker,
|
|
35809
|
+
stage: "deferred_turn_dropped",
|
|
35810
|
+
result: "info",
|
|
35811
|
+
reason: appIdMismatch ? "app_id_mismatch" : "project_id_mismatch",
|
|
35812
|
+
repoRoot,
|
|
35813
|
+
fields: {
|
|
35814
|
+
deferredTurnId: entry.record.turnId,
|
|
35815
|
+
deferredSessionId: entry.record.sessionId,
|
|
35816
|
+
appIdAtDefer: entry.record.appIdAtDefer,
|
|
35817
|
+
projectIdAtDefer: entry.record.projectIdAtDefer,
|
|
35818
|
+
currentAppId,
|
|
35819
|
+
currentProjectId
|
|
35820
|
+
}
|
|
35821
|
+
});
|
|
35822
|
+
continue;
|
|
35823
|
+
}
|
|
35824
|
+
liveEntries.push(entry);
|
|
35825
|
+
}
|
|
35826
|
+
if (droppedThisPass > 0) {
|
|
35827
|
+
droppedTotal += droppedThisPass;
|
|
35828
|
+
}
|
|
35829
|
+
if (liveEntries.length === 0) {
|
|
35830
|
+
const remaining = await listDeferredTurnsForRepo(repoRoot).catch(() => []);
|
|
35831
|
+
if (remaining.length === 0) {
|
|
35832
|
+
exitReason = "queue_empty";
|
|
35833
|
+
break;
|
|
35834
|
+
}
|
|
35835
|
+
await sleep5(DEFERRED_TURN_DRAIN_POLL_INTERVAL_MS);
|
|
35836
|
+
continue;
|
|
35837
|
+
}
|
|
35838
|
+
const attemptable = liveEntries.filter(
|
|
35417
35839
|
(e) => isCurrentBranchBound && (!e.record.branchAtDefer || e.record.branchAtDefer === currentBranch)
|
|
35418
35840
|
);
|
|
35419
35841
|
if (attemptable.length === 0) {
|
|
@@ -35462,6 +35884,8 @@ async function runStandaloneDeferredTurnDrainer(repoRoot) {
|
|
|
35462
35884
|
} else {
|
|
35463
35885
|
failedThisPass += 1;
|
|
35464
35886
|
failedTotal += 1;
|
|
35887
|
+
const outcome = await recordDeferredTurnFailedAttempt(entry.filePath).catch(() => null);
|
|
35888
|
+
const promoted = outcome?.promoted === true;
|
|
35465
35889
|
await appendHookDiagnosticsEvent({
|
|
35466
35890
|
hook: "deferredTurnDrainer",
|
|
35467
35891
|
sessionId: sessionMarker,
|
|
@@ -35472,9 +35896,43 @@ async function runStandaloneDeferredTurnDrainer(repoRoot) {
|
|
|
35472
35896
|
message: result.error instanceof Error ? result.error.message : String(result.error ?? ""),
|
|
35473
35897
|
fields: {
|
|
35474
35898
|
deferredTurnId: entry.record.turnId,
|
|
35475
|
-
deferredSessionId: entry.record.sessionId
|
|
35899
|
+
deferredSessionId: entry.record.sessionId,
|
|
35900
|
+
attemptCount: outcome?.promoted === false ? outcome.newAttemptCount : outcome?.promoted === true ? outcome.finalAttemptCount : null,
|
|
35901
|
+
promoted
|
|
35476
35902
|
}
|
|
35477
35903
|
});
|
|
35904
|
+
if (promoted) {
|
|
35905
|
+
const errorDetails = getDrainerErrorDetails(result.error);
|
|
35906
|
+
await dispatchFinalizeFailure({
|
|
35907
|
+
// The dispatcher only knows about the two real Claude hook
|
|
35908
|
+
// entrypoints. The standalone drainer is logically a
|
|
35909
|
+
// post-Stop background process and the marker we're about
|
|
35910
|
+
// to write is consumed by the next prompt's UserPromptSubmit
|
|
35911
|
+
// hook, so attributing the failure to "Stop" matches what
|
|
35912
|
+
// the user will see.
|
|
35913
|
+
hook: "Stop",
|
|
35914
|
+
sessionId: sessionMarker,
|
|
35915
|
+
turnId: entry.record.turnId,
|
|
35916
|
+
repoRoot,
|
|
35917
|
+
preflightCode: errorDetails.preflightCode,
|
|
35918
|
+
message: `Deferred turn could not be recorded after ${outcome?.finalAttemptCount ?? "max"} attempts: ${errorDetails.message}`,
|
|
35919
|
+
hint: errorDetails.hint
|
|
35920
|
+
}).catch(async (dispatchErr) => {
|
|
35921
|
+
await appendHookDiagnosticsEvent({
|
|
35922
|
+
hook: "deferredTurnDrainer",
|
|
35923
|
+
sessionId: sessionMarker,
|
|
35924
|
+
stage: "deferred_turn_promotion_dispatch_failed",
|
|
35925
|
+
result: "error",
|
|
35926
|
+
reason: "exception",
|
|
35927
|
+
repoRoot,
|
|
35928
|
+
message: dispatchErr instanceof Error ? dispatchErr.message : String(dispatchErr),
|
|
35929
|
+
fields: {
|
|
35930
|
+
deferredTurnId: entry.record.turnId,
|
|
35931
|
+
deferredSessionId: entry.record.sessionId
|
|
35932
|
+
}
|
|
35933
|
+
});
|
|
35934
|
+
});
|
|
35935
|
+
}
|
|
35478
35936
|
}
|
|
35479
35937
|
}
|
|
35480
35938
|
if (recordedThisPass > 0) {
|
|
@@ -35527,6 +35985,7 @@ async function runStandaloneDeferredTurnDrainer(repoRoot) {
|
|
|
35527
35985
|
fields: {
|
|
35528
35986
|
recordedTotal,
|
|
35529
35987
|
failedTotal,
|
|
35988
|
+
droppedTotal,
|
|
35530
35989
|
elapsedMs: Date.now() - startedAt
|
|
35531
35990
|
}
|
|
35532
35991
|
});
|
|
@@ -35555,34 +36014,13 @@ async function maybeRunDeferredTurnDrainerFromArgv() {
|
|
|
35555
36014
|
return true;
|
|
35556
36015
|
}
|
|
35557
36016
|
|
|
35558
|
-
// src/finalize-failure-marker.ts
|
|
35559
|
-
var import_promises23 = __toESM(require("fs/promises"), 1);
|
|
35560
|
-
var import_node_path10 = __toESM(require("path"), 1);
|
|
35561
|
-
var FINALIZE_FAILURE_MARKER_REL = import_node_path10.default.join(".remix", ".last-finalize-failure.json");
|
|
35562
|
-
function markerPath(repoRoot) {
|
|
35563
|
-
return import_node_path10.default.join(repoRoot, FINALIZE_FAILURE_MARKER_REL);
|
|
35564
|
-
}
|
|
35565
|
-
async function readFinalizeFailureMarker(repoRoot) {
|
|
35566
|
-
const raw = await import_promises23.default.readFile(markerPath(repoRoot), "utf8").catch(() => null);
|
|
35567
|
-
if (!raw) return null;
|
|
35568
|
-
try {
|
|
35569
|
-
const parsed = JSON.parse(raw);
|
|
35570
|
-
if (parsed.schemaVersion !== 1) return null;
|
|
35571
|
-
if (typeof parsed.repoRoot !== "string" || typeof parsed.failedAt !== "string") return null;
|
|
35572
|
-
if (typeof parsed.message !== "string") return null;
|
|
35573
|
-
return parsed;
|
|
35574
|
-
} catch {
|
|
35575
|
-
return null;
|
|
35576
|
-
}
|
|
35577
|
-
}
|
|
35578
|
-
|
|
35579
36017
|
// src/spawn-helpers.ts
|
|
35580
|
-
var
|
|
36018
|
+
var import_node_child_process7 = require("child_process");
|
|
35581
36019
|
function spawnDeferredTurnDrainer(repoRoot) {
|
|
35582
36020
|
const entrypoint = process.argv[1];
|
|
35583
36021
|
if (!entrypoint) return;
|
|
35584
36022
|
if (!repoRoot) return;
|
|
35585
|
-
const child = (0,
|
|
36023
|
+
const child = (0, import_node_child_process7.spawn)(
|
|
35586
36024
|
process.execPath,
|
|
35587
36025
|
[...process.execArgv, entrypoint, "--drain-deferred-turns", repoRoot],
|
|
35588
36026
|
{
|
|
@@ -35596,7 +36034,7 @@ function spawnDeferredTurnDrainer(repoRoot) {
|
|
|
35596
36034
|
|
|
35597
36035
|
// src/hook-utils.ts
|
|
35598
36036
|
var import_promises24 = __toESM(require("fs/promises"), 1);
|
|
35599
|
-
var
|
|
36037
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
35600
36038
|
async function readJsonStdin() {
|
|
35601
36039
|
const chunks = [];
|
|
35602
36040
|
for await (const chunk of process.stdin) {
|
|
@@ -35622,16 +36060,16 @@ function extractString(input, keys) {
|
|
|
35622
36060
|
}
|
|
35623
36061
|
async function findBoundRepo(startPath) {
|
|
35624
36062
|
if (!startPath) return null;
|
|
35625
|
-
let current =
|
|
36063
|
+
let current = import_node_path12.default.resolve(startPath);
|
|
35626
36064
|
let stats = await import_promises24.default.stat(current).catch(() => null);
|
|
35627
36065
|
if (stats?.isFile()) {
|
|
35628
|
-
current =
|
|
36066
|
+
current = import_node_path12.default.dirname(current);
|
|
35629
36067
|
}
|
|
35630
36068
|
while (true) {
|
|
35631
|
-
const bindingPath =
|
|
36069
|
+
const bindingPath = import_node_path12.default.join(current, ".remix", "config.json");
|
|
35632
36070
|
const bindingStats = await import_promises24.default.stat(bindingPath).catch(() => null);
|
|
35633
36071
|
if (bindingStats?.isFile()) return current;
|
|
35634
|
-
const parent =
|
|
36072
|
+
const parent = import_node_path12.default.dirname(current);
|
|
35635
36073
|
if (parent === current) return null;
|
|
35636
36074
|
current = parent;
|
|
35637
36075
|
}
|
|
@@ -35655,8 +36093,8 @@ function buildRuntimeStatusOverride() {
|
|
|
35655
36093
|
"Use `remix_collab_drain_finalize_queue` only for explicit recovery flows, such as status reporting `await_finalize` before a merge-related operation."
|
|
35656
36094
|
].join("\n");
|
|
35657
36095
|
}
|
|
35658
|
-
var COLLAB_INIT_LOG_REL =
|
|
35659
|
-
var COLLAB_INIT_SPAWN_LOCK_REL =
|
|
36096
|
+
var COLLAB_INIT_LOG_REL = import_node_path13.default.join(".remix", "collab-init.log");
|
|
36097
|
+
var COLLAB_INIT_SPAWN_LOCK_REL = import_node_path13.default.join(".remix", ".collab-init-spawning");
|
|
35660
36098
|
var COLLAB_INIT_SPAWN_LOCK_STALE_MS = 90 * 1e3;
|
|
35661
36099
|
function isPidAlive2(pid) {
|
|
35662
36100
|
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
@@ -35669,7 +36107,7 @@ function isPidAlive2(pid) {
|
|
|
35669
36107
|
}
|
|
35670
36108
|
function readLockPid(spawnLockPath) {
|
|
35671
36109
|
try {
|
|
35672
|
-
const raw = (0,
|
|
36110
|
+
const raw = (0, import_node_fs7.readFileSync)(spawnLockPath, "utf8").trim();
|
|
35673
36111
|
if (!raw) return null;
|
|
35674
36112
|
const pid = Number.parseInt(raw, 10);
|
|
35675
36113
|
return Number.isFinite(pid) && pid > 0 ? pid : null;
|
|
@@ -35678,20 +36116,20 @@ function readLockPid(spawnLockPath) {
|
|
|
35678
36116
|
}
|
|
35679
36117
|
}
|
|
35680
36118
|
function maybeAutoSpawnBranchInit(repoRoot) {
|
|
35681
|
-
const remixDir =
|
|
35682
|
-
const spawnLockPath =
|
|
35683
|
-
const logPath =
|
|
36119
|
+
const remixDir = import_node_path13.default.join(repoRoot, ".remix");
|
|
36120
|
+
const spawnLockPath = import_node_path13.default.join(repoRoot, COLLAB_INIT_SPAWN_LOCK_REL);
|
|
36121
|
+
const logPath = import_node_path13.default.join(repoRoot, COLLAB_INIT_LOG_REL);
|
|
35684
36122
|
try {
|
|
35685
|
-
if ((0,
|
|
36123
|
+
if ((0, import_node_fs7.existsSync)(spawnLockPath)) {
|
|
35686
36124
|
const lockPid = readLockPid(spawnLockPath);
|
|
35687
36125
|
const lockAlive = lockPid !== null && isPidAlive2(lockPid);
|
|
35688
|
-
const ageMs = Date.now() - (0,
|
|
36126
|
+
const ageMs = Date.now() - (0, import_node_fs7.statSync)(spawnLockPath).mtimeMs;
|
|
35689
36127
|
if (lockAlive && ageMs < COLLAB_INIT_SPAWN_LOCK_STALE_MS) {
|
|
35690
36128
|
return { spawned: false, reason: "spawn_lock_held" };
|
|
35691
36129
|
}
|
|
35692
36130
|
if (!lockAlive) {
|
|
35693
36131
|
try {
|
|
35694
|
-
(0,
|
|
36132
|
+
(0, import_node_fs7.unlinkSync)(spawnLockPath);
|
|
35695
36133
|
} catch {
|
|
35696
36134
|
}
|
|
35697
36135
|
}
|
|
@@ -35699,14 +36137,14 @@ function maybeAutoSpawnBranchInit(repoRoot) {
|
|
|
35699
36137
|
} catch {
|
|
35700
36138
|
}
|
|
35701
36139
|
try {
|
|
35702
|
-
(0,
|
|
36140
|
+
(0, import_node_fs7.mkdirSync)(remixDir, { recursive: true });
|
|
35703
36141
|
} catch {
|
|
35704
36142
|
}
|
|
35705
36143
|
let out;
|
|
35706
36144
|
let err;
|
|
35707
36145
|
try {
|
|
35708
|
-
out = (0,
|
|
35709
|
-
err = (0,
|
|
36146
|
+
out = (0, import_node_fs7.openSync)(logPath, "a");
|
|
36147
|
+
err = (0, import_node_fs7.openSync)(logPath, "a");
|
|
35710
36148
|
} catch (logErr) {
|
|
35711
36149
|
return {
|
|
35712
36150
|
spawned: false,
|
|
@@ -35715,7 +36153,7 @@ function maybeAutoSpawnBranchInit(repoRoot) {
|
|
|
35715
36153
|
};
|
|
35716
36154
|
}
|
|
35717
36155
|
try {
|
|
35718
|
-
const child = (0,
|
|
36156
|
+
const child = (0, import_node_child_process8.spawn)("remix", ["collab", "init"], {
|
|
35719
36157
|
cwd: repoRoot,
|
|
35720
36158
|
detached: true,
|
|
35721
36159
|
stdio: ["ignore", out, err],
|
|
@@ -35723,8 +36161,8 @@ function maybeAutoSpawnBranchInit(repoRoot) {
|
|
|
35723
36161
|
});
|
|
35724
36162
|
child.unref();
|
|
35725
36163
|
try {
|
|
35726
|
-
(0,
|
|
35727
|
-
(0,
|
|
36164
|
+
(0, import_node_fs7.writeFileSync)(spawnLockPath, String(child.pid ?? ""), "utf8");
|
|
36165
|
+
(0, import_node_fs7.utimesSync)(spawnLockPath, /* @__PURE__ */ new Date(), /* @__PURE__ */ new Date());
|
|
35728
36166
|
} catch {
|
|
35729
36167
|
}
|
|
35730
36168
|
return { spawned: true, pid: child.pid, logPath };
|
|
@@ -35742,7 +36180,7 @@ function buildBranchInitContextMessage(branch, repoRoot, logPath) {
|
|
|
35742
36180
|
"[Remix recovery in progress]:",
|
|
35743
36181
|
`Remix is initializing recording for ${branchLabel} in ${repoRoot} in the background.`,
|
|
35744
36182
|
"This turn may be recorded retroactively once init finishes (it may appear in the timeline with a small delay).",
|
|
35745
|
-
"Do NOT call any Remix MCP tool to initialize,
|
|
36183
|
+
"Do NOT call any Remix MCP tool to initialize, repair, or sync this branch \u2014 the plugin is handling it automatically.",
|
|
35746
36184
|
`Init log: ${logPath}`
|
|
35747
36185
|
].join("\n");
|
|
35748
36186
|
}
|