@remixhq/claude-plugin 0.1.22 → 0.1.24
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 +536 -86
- package/dist/hook-stop-collab.cjs.map +1 -1
- package/dist/hook-user-prompt.cjs +1047 -514
- 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 +508 -519
- 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,
|
|
@@ -8766,6 +8827,15 @@ function shouldRequireRemoteLaneForCurrentBranch(params) {
|
|
|
8766
8827
|
if (params.currentBranch === defaultBranch) return false;
|
|
8767
8828
|
return !params.binding.laneId || params.binding.currentAppId === params.binding.upstreamAppId;
|
|
8768
8829
|
}
|
|
8830
|
+
function resolveLaneLookupProjectId(params) {
|
|
8831
|
+
const currentBranch = normalizeBranchName2(params.currentBranch);
|
|
8832
|
+
const defaultBranch = normalizeBranchName2(params.defaultBranch);
|
|
8833
|
+
const localProjectId = params.localBinding.projectId ?? null;
|
|
8834
|
+
if (currentBranch && currentBranch !== defaultBranch && localProjectId) {
|
|
8835
|
+
return localProjectId;
|
|
8836
|
+
}
|
|
8837
|
+
return params.explicitRootProjectId ?? (params.requireRemoteLane ? void 0 : localProjectId ?? params.fallbackProjectId ?? void 0);
|
|
8838
|
+
}
|
|
8769
8839
|
async function persistResolvedLane(repoRoot, binding) {
|
|
8770
8840
|
await writeCollabBinding(repoRoot, {
|
|
8771
8841
|
projectId: binding.projectId,
|
|
@@ -8844,7 +8914,14 @@ async function resolveActiveLaneBindingUncached(params, state) {
|
|
|
8844
8914
|
};
|
|
8845
8915
|
}
|
|
8846
8916
|
const laneResp2 = await params.api.resolveProjectLaneBinding({
|
|
8847
|
-
projectId:
|
|
8917
|
+
projectId: resolveLaneLookupProjectId({
|
|
8918
|
+
explicitRootProjectId: state.explicitRootBinding?.projectId,
|
|
8919
|
+
localBinding,
|
|
8920
|
+
currentBranch,
|
|
8921
|
+
defaultBranch: state.defaultBranch,
|
|
8922
|
+
requireRemoteLane,
|
|
8923
|
+
fallbackProjectId: state.projectId
|
|
8924
|
+
}),
|
|
8848
8925
|
repoFingerprint: state.repoFingerprint ?? void 0,
|
|
8849
8926
|
remoteUrl: state.remoteUrl ?? void 0,
|
|
8850
8927
|
defaultBranch: state.defaultBranch ?? void 0,
|
|
@@ -9003,6 +9080,8 @@ function buildBaseState() {
|
|
|
9003
9080
|
branchName: null,
|
|
9004
9081
|
localCommitHash: null,
|
|
9005
9082
|
currentSnapshotHash: null,
|
|
9083
|
+
currentServerRevisionId: null,
|
|
9084
|
+
currentServerTreeHash: null,
|
|
9006
9085
|
currentServerHeadHash: null,
|
|
9007
9086
|
currentServerHeadCommitId: null,
|
|
9008
9087
|
worktreeClean: false,
|
|
@@ -9036,6 +9115,8 @@ function buildBaseState() {
|
|
|
9036
9115
|
baseline: {
|
|
9037
9116
|
lastSnapshotId: null,
|
|
9038
9117
|
lastSnapshotHash: null,
|
|
9118
|
+
lastServerRevisionId: null,
|
|
9119
|
+
lastServerTreeHash: null,
|
|
9039
9120
|
lastServerHeadHash: null,
|
|
9040
9121
|
lastSeenLocalCommitHash: null
|
|
9041
9122
|
}
|
|
@@ -9162,6 +9243,8 @@ async function collabDetectRepoState(params) {
|
|
|
9162
9243
|
summarizeAsyncJobs({ repoRoot, branchName: binding.branchName ?? null })
|
|
9163
9244
|
]);
|
|
9164
9245
|
const appHead = unwrapResponseObject(headResp, "app head");
|
|
9246
|
+
detected.currentServerRevisionId = appHead.headRevisionId ?? null;
|
|
9247
|
+
detected.currentServerTreeHash = appHead.treeHash ?? null;
|
|
9165
9248
|
detected.currentServerHeadHash = appHead.headCommitHash;
|
|
9166
9249
|
detected.currentServerHeadCommitId = appHead.headCommitId;
|
|
9167
9250
|
detected.currentSnapshotHash = inspection.snapshotHash;
|
|
@@ -9170,6 +9253,8 @@ async function collabDetectRepoState(params) {
|
|
|
9170
9253
|
detected.baseline = {
|
|
9171
9254
|
lastSnapshotId: baseline?.lastSnapshotId ?? null,
|
|
9172
9255
|
lastSnapshotHash: baseline?.lastSnapshotHash ?? null,
|
|
9256
|
+
lastServerRevisionId: baseline?.lastServerRevisionId ?? null,
|
|
9257
|
+
lastServerTreeHash: baseline?.lastServerTreeHash ?? null,
|
|
9173
9258
|
lastServerHeadHash: baseline?.lastServerHeadHash ?? null,
|
|
9174
9259
|
lastSeenLocalCommitHash: baseline?.lastSeenLocalCommitHash ?? null
|
|
9175
9260
|
};
|
|
@@ -9179,6 +9264,7 @@ async function collabDetectRepoState(params) {
|
|
|
9179
9264
|
const bootstrapResp = await params.api.getAppDelta(binding.currentAppId, {
|
|
9180
9265
|
baseHeadHash: localCommitHash,
|
|
9181
9266
|
targetHeadHash: appHead.headCommitHash,
|
|
9267
|
+
targetRevisionId: appHead.headRevisionId,
|
|
9182
9268
|
repoFingerprint: binding.repoFingerprint ?? void 0,
|
|
9183
9269
|
remoteUrl: binding.remoteUrl ?? void 0,
|
|
9184
9270
|
defaultBranch: binding.defaultBranch ?? void 0
|
|
@@ -9201,7 +9287,7 @@ async function collabDetectRepoState(params) {
|
|
|
9201
9287
|
}
|
|
9202
9288
|
}
|
|
9203
9289
|
detected.repoState = "external_local_base_changed";
|
|
9204
|
-
detected.hint = "No local Remix baseline exists for this lane yet. Run `remix collab
|
|
9290
|
+
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
9291
|
return detected;
|
|
9206
9292
|
}
|
|
9207
9293
|
const localHeadMovedSinceBaseline = Boolean(baseline.lastSeenLocalCommitHash) && localCommitHash !== baseline.lastSeenLocalCommitHash;
|
|
@@ -9220,7 +9306,30 @@ async function collabDetectRepoState(params) {
|
|
|
9220
9306
|
return detected;
|
|
9221
9307
|
}
|
|
9222
9308
|
const localChanged = inspection.snapshotHash !== baseline.lastSnapshotHash;
|
|
9223
|
-
const
|
|
9309
|
+
const serverHeadChanged = appHead.headCommitHash !== baseline.lastServerHeadHash;
|
|
9310
|
+
const revisionChanged = Boolean(
|
|
9311
|
+
baseline.lastServerRevisionId && (appHead.headRevisionId ?? null) !== baseline.lastServerRevisionId
|
|
9312
|
+
);
|
|
9313
|
+
const equivalentRevisionDrift = revisionChanged && !serverHeadChanged;
|
|
9314
|
+
if (equivalentRevisionDrift) {
|
|
9315
|
+
await writeLocalBaseline({
|
|
9316
|
+
repoRoot,
|
|
9317
|
+
repoFingerprint: binding.repoFingerprint,
|
|
9318
|
+
laneId: binding.laneId,
|
|
9319
|
+
currentAppId: binding.currentAppId,
|
|
9320
|
+
branchName: binding.branchName,
|
|
9321
|
+
lastSnapshotId: baseline.lastSnapshotId,
|
|
9322
|
+
lastSnapshotHash: baseline.lastSnapshotHash,
|
|
9323
|
+
lastServerRevisionId: appHead.headRevisionId ?? null,
|
|
9324
|
+
lastServerTreeHash: appHead.treeHash ?? baseline.lastServerTreeHash ?? null,
|
|
9325
|
+
lastServerHeadHash: appHead.headCommitHash,
|
|
9326
|
+
lastSeenLocalCommitHash: baseline.lastSeenLocalCommitHash
|
|
9327
|
+
});
|
|
9328
|
+
detected.baseline.lastServerRevisionId = appHead.headRevisionId ?? null;
|
|
9329
|
+
detected.baseline.lastServerTreeHash = appHead.treeHash ?? baseline.lastServerTreeHash ?? null;
|
|
9330
|
+
detected.baseline.lastServerHeadHash = appHead.headCommitHash;
|
|
9331
|
+
}
|
|
9332
|
+
const serverChanged = serverHeadChanged;
|
|
9224
9333
|
if (!localChanged && !serverChanged) {
|
|
9225
9334
|
detected.repoState = "idle";
|
|
9226
9335
|
return detected;
|
|
@@ -9644,6 +9753,7 @@ function buildWorkspaceMetadata(params) {
|
|
|
9644
9753
|
recordingMode: "boundary_delta",
|
|
9645
9754
|
baselineSnapshotId: params.baselineSnapshotId,
|
|
9646
9755
|
currentSnapshotId: params.currentSnapshotId,
|
|
9756
|
+
baselineServerRevisionId: params.baselineServerRevisionId ?? null,
|
|
9647
9757
|
baselineServerHeadHash: params.baselineServerHeadHash,
|
|
9648
9758
|
currentSnapshotHash: params.currentSnapshotHash,
|
|
9649
9759
|
localCommitHash: params.localCommitHash,
|
|
@@ -9662,6 +9772,59 @@ function buildWorkspaceMetadata(params) {
|
|
|
9662
9772
|
}
|
|
9663
9773
|
return metadata;
|
|
9664
9774
|
}
|
|
9775
|
+
async function findExistingChangeStepByIdempotency(params) {
|
|
9776
|
+
const idempotencyKey = params.idempotencyKey?.trim();
|
|
9777
|
+
if (!idempotencyKey) return null;
|
|
9778
|
+
const resp = await params.api.listChangeSteps(params.appId, { limit: 1, idempotencyKey });
|
|
9779
|
+
const responseObject = unwrapResponseObject(
|
|
9780
|
+
resp,
|
|
9781
|
+
"change step list"
|
|
9782
|
+
);
|
|
9783
|
+
const steps = Array.isArray(responseObject) ? responseObject : Array.isArray(responseObject.items) ? responseObject.items : [];
|
|
9784
|
+
return steps.find((step) => step.idempotencyKey === idempotencyKey) ?? null;
|
|
9785
|
+
}
|
|
9786
|
+
async function writeBaselineFromSucceededChangeStep(params) {
|
|
9787
|
+
const nextServerHeadHash = typeof params.changeStep.headCommitHash === "string" ? params.changeStep.headCommitHash.trim() : "";
|
|
9788
|
+
if (!nextServerHeadHash) {
|
|
9789
|
+
throw buildFinalizeCliError({
|
|
9790
|
+
message: "Backend returned a succeeded change step without a head commit hash.",
|
|
9791
|
+
exitCode: 1,
|
|
9792
|
+
hint: "This is a backend invariant violation; retry will not help. Run `remix collab status` before trying again.",
|
|
9793
|
+
disposition: "terminal",
|
|
9794
|
+
reason: "missing_head_commit_hash"
|
|
9795
|
+
});
|
|
9796
|
+
}
|
|
9797
|
+
let nextServerRevisionId = typeof params.changeStep.resultRevisionId === "string" ? params.changeStep.resultRevisionId.trim() : "";
|
|
9798
|
+
let nextServerTreeHash = null;
|
|
9799
|
+
if (!nextServerRevisionId) {
|
|
9800
|
+
const freshHeadResp = await params.api.getAppHead(params.job.currentAppId);
|
|
9801
|
+
const freshHead = unwrapResponseObject(freshHeadResp, "app head");
|
|
9802
|
+
if (freshHead.headCommitHash !== nextServerHeadHash || !freshHead.headRevisionId) {
|
|
9803
|
+
throw buildFinalizeCliError({
|
|
9804
|
+
message: "Backend returned a succeeded change step without a matching result revision.",
|
|
9805
|
+
exitCode: 1,
|
|
9806
|
+
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`.",
|
|
9807
|
+
disposition: "terminal",
|
|
9808
|
+
reason: "missing_result_revision_id"
|
|
9809
|
+
});
|
|
9810
|
+
}
|
|
9811
|
+
nextServerRevisionId = freshHead.headRevisionId;
|
|
9812
|
+
nextServerTreeHash = freshHead.treeHash ?? null;
|
|
9813
|
+
}
|
|
9814
|
+
await writeLocalBaseline({
|
|
9815
|
+
repoRoot: params.job.repoRoot,
|
|
9816
|
+
repoFingerprint: params.job.repoFingerprint,
|
|
9817
|
+
laneId: params.job.laneId,
|
|
9818
|
+
currentAppId: params.job.currentAppId,
|
|
9819
|
+
branchName: params.job.branchName,
|
|
9820
|
+
lastSnapshotId: params.snapshot.id,
|
|
9821
|
+
lastSnapshotHash: params.snapshot.snapshotHash,
|
|
9822
|
+
lastServerRevisionId: nextServerRevisionId,
|
|
9823
|
+
lastServerTreeHash: nextServerTreeHash,
|
|
9824
|
+
lastServerHeadHash: nextServerHeadHash,
|
|
9825
|
+
lastSeenLocalCommitHash: params.snapshot.localCommitHash
|
|
9826
|
+
});
|
|
9827
|
+
}
|
|
9665
9828
|
async function harvestPreTurnEvents(repoRoot, fromCommit, toCommit) {
|
|
9666
9829
|
if (!toCommit) return null;
|
|
9667
9830
|
try {
|
|
@@ -9722,12 +9885,12 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
9722
9885
|
throw buildFinalizeCliError({
|
|
9723
9886
|
message: "Local baseline is missing for this queued finalize job.",
|
|
9724
9887
|
exitCode: 2,
|
|
9725
|
-
hint: "Run `remix collab
|
|
9888
|
+
hint: "Run `remix collab init` to seed this checkout's revision baseline.",
|
|
9726
9889
|
disposition: "terminal",
|
|
9727
9890
|
reason: "baseline_missing"
|
|
9728
9891
|
});
|
|
9729
9892
|
}
|
|
9730
|
-
const baselineDrifted = baseline.lastSnapshotId !== job.baselineSnapshotId || baseline.lastServerHeadHash !== job.baselineServerHeadHash;
|
|
9893
|
+
const baselineDrifted = baseline.lastSnapshotId !== job.baselineSnapshotId || (job.baselineServerRevisionId ? baseline.lastServerRevisionId !== job.baselineServerRevisionId : false) || baseline.lastServerHeadHash !== job.baselineServerHeadHash;
|
|
9731
9894
|
const appHead = unwrapResponseObject(appHeadResp, "app head");
|
|
9732
9895
|
const remoteUrl = readMetadataString(job, "remoteUrl");
|
|
9733
9896
|
const defaultBranch = readMetadataString(job, "defaultBranch");
|
|
@@ -9750,12 +9913,13 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
9750
9913
|
throw buildFinalizeCliError({
|
|
9751
9914
|
message: "Finalize queue baseline drifted before this job was processed.",
|
|
9752
9915
|
exitCode: 1,
|
|
9753
|
-
hint: "Process queued finalize jobs in capture order, or
|
|
9916
|
+
hint: "Process queued finalize jobs in capture order, or run `remix collab init` to refresh the revision baseline before retrying.",
|
|
9754
9917
|
disposition: "terminal",
|
|
9755
9918
|
reason: "baseline_drifted"
|
|
9756
9919
|
});
|
|
9757
9920
|
}
|
|
9758
|
-
|
|
9921
|
+
const serverStillAtBaseline = job.baselineServerRevisionId ? appHead.headRevisionId === job.baselineServerRevisionId : appHead.headCommitHash === job.baselineServerHeadHash;
|
|
9922
|
+
if (!serverStillAtBaseline) {
|
|
9759
9923
|
throw buildFinalizeCliError({
|
|
9760
9924
|
message: "Server lane changed before a no-diff turn could be recorded.",
|
|
9761
9925
|
exitCode: 2,
|
|
@@ -9777,6 +9941,7 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
9777
9941
|
defaultBranch,
|
|
9778
9942
|
baselineSnapshotId: job.baselineSnapshotId,
|
|
9779
9943
|
currentSnapshotId: job.currentSnapshotId,
|
|
9944
|
+
baselineServerRevisionId: job.baselineServerRevisionId,
|
|
9780
9945
|
baselineServerHeadHash: job.baselineServerHeadHash,
|
|
9781
9946
|
currentSnapshotHash: snapshot.snapshotHash,
|
|
9782
9947
|
localCommitHash: snapshot.localCommitHash,
|
|
@@ -9797,6 +9962,8 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
9797
9962
|
branchName: job.branchName,
|
|
9798
9963
|
lastSnapshotId: snapshot.id,
|
|
9799
9964
|
lastSnapshotHash: snapshot.snapshotHash,
|
|
9965
|
+
lastServerRevisionId: appHead.headRevisionId ?? null,
|
|
9966
|
+
lastServerTreeHash: appHead.treeHash ?? null,
|
|
9800
9967
|
lastServerHeadHash: appHead.headCommitHash,
|
|
9801
9968
|
lastSeenLocalCommitHash: snapshot.localCommitHash
|
|
9802
9969
|
});
|
|
@@ -9817,14 +9984,14 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
9817
9984
|
};
|
|
9818
9985
|
}
|
|
9819
9986
|
const localBaselineAdvanced = baseline.lastSnapshotId !== job.baselineSnapshotId;
|
|
9820
|
-
const serverHeadAdvanced = appHead.headCommitHash !== job.baselineServerHeadHash;
|
|
9987
|
+
const serverHeadAdvanced = job.baselineServerRevisionId ? appHead.headRevisionId !== job.baselineServerRevisionId : appHead.headCommitHash !== job.baselineServerHeadHash;
|
|
9821
9988
|
if (baselineDrifted) {
|
|
9822
9989
|
const consistentAdvance = localBaselineAdvanced && serverHeadAdvanced;
|
|
9823
9990
|
if (!consistentAdvance) {
|
|
9824
9991
|
throw buildFinalizeCliError({
|
|
9825
9992
|
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
9993
|
exitCode: 1,
|
|
9827
|
-
hint: "Run `remix collab status` to inspect, then
|
|
9994
|
+
hint: "Run `remix collab status` to inspect, then sync or reconcile before retrying.",
|
|
9828
9995
|
disposition: "terminal",
|
|
9829
9996
|
reason: "baseline_drifted"
|
|
9830
9997
|
});
|
|
@@ -9832,6 +9999,7 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
9832
9999
|
}
|
|
9833
10000
|
let submissionDiff = diffResult.diff;
|
|
9834
10001
|
let submissionBaseHeadHash = job.baselineServerHeadHash;
|
|
10002
|
+
let submissionBaseRevisionId = job.baselineServerRevisionId;
|
|
9835
10003
|
let replayedFromBaseHash = null;
|
|
9836
10004
|
if (!submissionBaseHeadHash) {
|
|
9837
10005
|
throw buildFinalizeCliError({
|
|
@@ -9842,6 +10010,34 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
9842
10010
|
});
|
|
9843
10011
|
}
|
|
9844
10012
|
const replayNeeded = appHead.headCommitHash !== submissionBaseHeadHash || baselineDrifted;
|
|
10013
|
+
if (replayNeeded) {
|
|
10014
|
+
const existingChangeStep = await findExistingChangeStepByIdempotency({
|
|
10015
|
+
api: params.api,
|
|
10016
|
+
appId: job.currentAppId,
|
|
10017
|
+
idempotencyKey: job.idempotencyKey
|
|
10018
|
+
});
|
|
10019
|
+
if (existingChangeStep) {
|
|
10020
|
+
const changeStep2 = existingChangeStep.status === "succeeded" ? existingChangeStep : await pollChangeStep(params.api, job.currentAppId, existingChangeStep.id);
|
|
10021
|
+
invalidateAppHeadCache(job.currentAppId);
|
|
10022
|
+
invalidateAppDeltaCacheForApp(job.currentAppId);
|
|
10023
|
+
await writeBaselineFromSucceededChangeStep({ api: params.api, job, snapshot, changeStep: changeStep2 });
|
|
10024
|
+
await updatePendingFinalizeJob(job.id, {
|
|
10025
|
+
status: "completed",
|
|
10026
|
+
metadata: { changeStepId: String(changeStep2.id ?? "") }
|
|
10027
|
+
});
|
|
10028
|
+
return {
|
|
10029
|
+
mode: "changed_turn",
|
|
10030
|
+
idempotencyKey: job.idempotencyKey ?? "",
|
|
10031
|
+
queued: false,
|
|
10032
|
+
jobId: job.id,
|
|
10033
|
+
repoState,
|
|
10034
|
+
changeStep: changeStep2,
|
|
10035
|
+
collabTurn: null,
|
|
10036
|
+
autoSync: null,
|
|
10037
|
+
warnings: []
|
|
10038
|
+
};
|
|
10039
|
+
}
|
|
10040
|
+
}
|
|
9845
10041
|
if (replayNeeded) {
|
|
9846
10042
|
try {
|
|
9847
10043
|
const replayResp = await params.api.startChangeStepReplay(job.currentAppId, {
|
|
@@ -9849,7 +10045,9 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
9849
10045
|
assistantResponse: job.assistantResponse,
|
|
9850
10046
|
diff: diffResult.diff,
|
|
9851
10047
|
baseCommitHash: submissionBaseHeadHash,
|
|
10048
|
+
baseRevisionId: job.baselineServerRevisionId,
|
|
9852
10049
|
targetHeadCommitHash: appHead.headCommitHash,
|
|
10050
|
+
targetRevisionId: appHead.headRevisionId,
|
|
9853
10051
|
expectedPaths: diffResult.changedPaths,
|
|
9854
10052
|
actor,
|
|
9855
10053
|
workspaceMetadata: buildWorkspaceMetadata({
|
|
@@ -9859,6 +10057,7 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
9859
10057
|
defaultBranch,
|
|
9860
10058
|
baselineSnapshotId: job.baselineSnapshotId,
|
|
9861
10059
|
currentSnapshotId: job.currentSnapshotId,
|
|
10060
|
+
baselineServerRevisionId: job.baselineServerRevisionId,
|
|
9862
10061
|
baselineServerHeadHash: job.baselineServerHeadHash,
|
|
9863
10062
|
currentSnapshotHash: snapshot.snapshotHash,
|
|
9864
10063
|
localCommitHash: snapshot.localCommitHash,
|
|
@@ -9884,6 +10083,7 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
9884
10083
|
submissionDiff = replayDiff.diff;
|
|
9885
10084
|
replayedFromBaseHash = submissionBaseHeadHash;
|
|
9886
10085
|
submissionBaseHeadHash = appHead.headCommitHash;
|
|
10086
|
+
submissionBaseRevisionId = appHead.headRevisionId;
|
|
9887
10087
|
} catch (error) {
|
|
9888
10088
|
if (error instanceof RemixError && error.finalizeDisposition === void 0) {
|
|
9889
10089
|
const detail = error.hint ? `${error.message} (${error.hint})` : error.message;
|
|
@@ -9905,6 +10105,7 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
9905
10105
|
assistantResponse: job.assistantResponse,
|
|
9906
10106
|
diff: submissionDiff,
|
|
9907
10107
|
baseCommitHash: submissionBaseHeadHash,
|
|
10108
|
+
baseRevisionId: submissionBaseRevisionId,
|
|
9908
10109
|
headCommitHash: submissionBaseHeadHash,
|
|
9909
10110
|
changedFilesCount: diffResult.stats.changedFilesCount,
|
|
9910
10111
|
insertions: diffResult.stats.insertions,
|
|
@@ -9917,6 +10118,7 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
9917
10118
|
defaultBranch,
|
|
9918
10119
|
baselineSnapshotId: job.baselineSnapshotId,
|
|
9919
10120
|
currentSnapshotId: job.currentSnapshotId,
|
|
10121
|
+
baselineServerRevisionId: job.baselineServerRevisionId,
|
|
9920
10122
|
baselineServerHeadHash: job.baselineServerHeadHash,
|
|
9921
10123
|
currentSnapshotHash: snapshot.snapshotHash,
|
|
9922
10124
|
localCommitHash: snapshot.localCommitHash,
|
|
@@ -9933,27 +10135,7 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
9933
10135
|
const changeStep = await pollChangeStep(params.api, job.currentAppId, String(createdStep.id));
|
|
9934
10136
|
invalidateAppHeadCache(job.currentAppId);
|
|
9935
10137
|
invalidateAppDeltaCacheForApp(job.currentAppId);
|
|
9936
|
-
|
|
9937
|
-
if (!nextServerHeadHash) {
|
|
9938
|
-
throw buildFinalizeCliError({
|
|
9939
|
-
message: "Backend returned a succeeded change step without a head commit hash.",
|
|
9940
|
-
exitCode: 1,
|
|
9941
|
-
hint: "This is a backend invariant violation; retry will not help. Re-anchor and try again.",
|
|
9942
|
-
disposition: "terminal",
|
|
9943
|
-
reason: "missing_head_commit_hash"
|
|
9944
|
-
});
|
|
9945
|
-
}
|
|
9946
|
-
await writeLocalBaseline({
|
|
9947
|
-
repoRoot: job.repoRoot,
|
|
9948
|
-
repoFingerprint: job.repoFingerprint,
|
|
9949
|
-
laneId: job.laneId,
|
|
9950
|
-
currentAppId: job.currentAppId,
|
|
9951
|
-
branchName: job.branchName,
|
|
9952
|
-
lastSnapshotId: snapshot.id,
|
|
9953
|
-
lastSnapshotHash: snapshot.snapshotHash,
|
|
9954
|
-
lastServerHeadHash: nextServerHeadHash,
|
|
9955
|
-
lastSeenLocalCommitHash: snapshot.localCommitHash
|
|
9956
|
-
});
|
|
10138
|
+
await writeBaselineFromSucceededChangeStep({ api: params.api, job, snapshot, changeStep });
|
|
9957
10139
|
await updatePendingFinalizeJob(job.id, {
|
|
9958
10140
|
status: "completed",
|
|
9959
10141
|
metadata: { changeStepId: String(changeStep.id ?? "") }
|
|
@@ -9982,6 +10164,7 @@ async function enqueueCapturedFinalizeTurn(params) {
|
|
|
9982
10164
|
prompt: params.prompt,
|
|
9983
10165
|
assistantResponse: params.assistantResponse,
|
|
9984
10166
|
baselineSnapshotId: params.baselineSnapshotId,
|
|
10167
|
+
baselineServerRevisionId: params.baselineServerRevisionId ?? null,
|
|
9985
10168
|
baselineServerHeadHash: params.baselineServerHeadHash,
|
|
9986
10169
|
currentSnapshotId: params.currentSnapshotId,
|
|
9987
10170
|
idempotencyKey: params.idempotencyKey,
|
|
@@ -10080,17 +10263,6 @@ async function collabFinalizeTurn(params) {
|
|
|
10080
10263
|
});
|
|
10081
10264
|
}
|
|
10082
10265
|
}
|
|
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
10266
|
const detected = await collabDetectRepoState({
|
|
10095
10267
|
api: params.api,
|
|
10096
10268
|
cwd: repoRoot,
|
|
@@ -10131,9 +10303,16 @@ async function collabFinalizeTurn(params) {
|
|
|
10131
10303
|
hint: detected.hint
|
|
10132
10304
|
});
|
|
10133
10305
|
}
|
|
10306
|
+
if (detected.repoState === "both_changed") {
|
|
10307
|
+
throw new RemixError("Local and server changes must be reconciled before finalizing this turn.", {
|
|
10308
|
+
code: "reconcile_required",
|
|
10309
|
+
exitCode: 2,
|
|
10310
|
+
hint: detected.hint || "Run `remix collab reconcile --dry-run` to inspect recovery options before retrying."
|
|
10311
|
+
});
|
|
10312
|
+
}
|
|
10134
10313
|
if (detected.repoState === "external_local_base_changed") {
|
|
10135
|
-
throw new RemixError("The local checkout
|
|
10136
|
-
code: "
|
|
10314
|
+
throw new RemixError("The local checkout is missing a Remix revision baseline for this lane.", {
|
|
10315
|
+
code: "baseline_missing",
|
|
10137
10316
|
exitCode: 2,
|
|
10138
10317
|
hint: detected.hint
|
|
10139
10318
|
});
|
|
@@ -10145,8 +10324,9 @@ async function collabFinalizeTurn(params) {
|
|
|
10145
10324
|
});
|
|
10146
10325
|
if (!baseline) {
|
|
10147
10326
|
throw new RemixError("Local Remix baseline is missing for this lane.", {
|
|
10327
|
+
code: "baseline_missing",
|
|
10148
10328
|
exitCode: 2,
|
|
10149
|
-
hint: "Run `remix collab
|
|
10329
|
+
hint: "Run `remix collab init` or sync this lane to create a fresh revision baseline."
|
|
10150
10330
|
});
|
|
10151
10331
|
}
|
|
10152
10332
|
const snapshot = await captureLocalSnapshot({
|
|
@@ -10157,10 +10337,11 @@ async function collabFinalizeTurn(params) {
|
|
|
10157
10337
|
});
|
|
10158
10338
|
const mode = snapshot.snapshotHash === baseline.lastSnapshotHash ? "no_diff_turn" : "changed_turn";
|
|
10159
10339
|
const idempotencyKey = params.idempotencyKey?.trim() || buildDeterministicIdempotencyKey({
|
|
10160
|
-
kind: "
|
|
10340
|
+
kind: "collab_finalize_turn_boundary_v2",
|
|
10161
10341
|
appId: binding.currentAppId,
|
|
10162
10342
|
laneId: binding.laneId,
|
|
10163
10343
|
baselineSnapshotId: baseline.lastSnapshotId,
|
|
10344
|
+
baselineServerRevisionId: baseline.lastServerRevisionId,
|
|
10164
10345
|
baselineServerHeadHash: baseline.lastServerHeadHash,
|
|
10165
10346
|
currentSnapshotId: snapshot.id,
|
|
10166
10347
|
currentSnapshotHash: snapshot.snapshotHash,
|
|
@@ -10180,6 +10361,7 @@ async function collabFinalizeTurn(params) {
|
|
|
10180
10361
|
prompt,
|
|
10181
10362
|
assistantResponse,
|
|
10182
10363
|
baselineSnapshotId: baseline.lastSnapshotId,
|
|
10364
|
+
baselineServerRevisionId: baseline.lastServerRevisionId,
|
|
10183
10365
|
baselineServerHeadHash: baseline.lastServerHeadHash,
|
|
10184
10366
|
currentSnapshotId: snapshot.id,
|
|
10185
10367
|
idempotencyKey,
|
|
@@ -10226,13 +10408,538 @@ var FINALIZE_PREFLIGHT_FAILURE_CODES = [
|
|
|
10226
10408
|
// Server has commits we don't. Fix: `remix collab sync` (safe to
|
|
10227
10409
|
// auto-run for fast-forward; non-FF refused by the command itself).
|
|
10228
10410
|
"pull_required",
|
|
10229
|
-
//
|
|
10230
|
-
|
|
10231
|
-
|
|
10411
|
+
// Both local and server changed. Fix: inspect and apply reconcile.
|
|
10412
|
+
"reconcile_required",
|
|
10413
|
+
// Local revision baseline is missing. Fix: `remix collab init` or sync.
|
|
10414
|
+
"baseline_missing"
|
|
10232
10415
|
];
|
|
10233
10416
|
var CODE_SET = new Set(FINALIZE_PREFLIGHT_FAILURE_CODES);
|
|
10417
|
+
function isFinalizePreflightFailureCode(value) {
|
|
10418
|
+
return typeof value === "string" && CODE_SET.has(value);
|
|
10419
|
+
}
|
|
10420
|
+
|
|
10421
|
+
// src/auto-fix-dispatcher.ts
|
|
10422
|
+
var import_node_child_process6 = require("child_process");
|
|
10423
|
+
var import_node_fs6 = require("fs");
|
|
10424
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
10425
|
+
|
|
10426
|
+
// src/finalize-failure-marker.ts
|
|
10427
|
+
var import_promises19 = __toESM(require("fs/promises"), 1);
|
|
10428
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
10429
|
+
var FINALIZE_FAILURE_MARKER_REL = import_node_path7.default.join(".remix", ".last-finalize-failure.json");
|
|
10430
|
+
function markerPath(repoRoot) {
|
|
10431
|
+
return import_node_path7.default.join(repoRoot, FINALIZE_FAILURE_MARKER_REL);
|
|
10432
|
+
}
|
|
10433
|
+
async function readFinalizeFailureMarker(repoRoot) {
|
|
10434
|
+
const raw = await import_promises19.default.readFile(markerPath(repoRoot), "utf8").catch(() => null);
|
|
10435
|
+
if (!raw) return null;
|
|
10436
|
+
try {
|
|
10437
|
+
const parsed = JSON.parse(raw);
|
|
10438
|
+
if (parsed.schemaVersion !== 1) return null;
|
|
10439
|
+
if (typeof parsed.repoRoot !== "string" || typeof parsed.failedAt !== "string") return null;
|
|
10440
|
+
if (typeof parsed.message !== "string") return null;
|
|
10441
|
+
return parsed;
|
|
10442
|
+
} catch {
|
|
10443
|
+
return null;
|
|
10444
|
+
}
|
|
10445
|
+
}
|
|
10446
|
+
async function writeFinalizeFailureMarker(marker) {
|
|
10447
|
+
const filePath = markerPath(marker.repoRoot);
|
|
10448
|
+
await import_promises19.default.mkdir(import_node_path7.default.dirname(filePath), { recursive: true });
|
|
10449
|
+
const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
10450
|
+
await import_promises19.default.writeFile(tmpPath, JSON.stringify(marker, null, 2), "utf8");
|
|
10451
|
+
await import_promises19.default.rename(tmpPath, filePath);
|
|
10452
|
+
}
|
|
10453
|
+
function buildFreshFailureMarker(params) {
|
|
10454
|
+
return {
|
|
10455
|
+
schemaVersion: 1,
|
|
10456
|
+
failedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10457
|
+
repoRoot: params.repoRoot,
|
|
10458
|
+
preflightCode: params.preflightCode,
|
|
10459
|
+
message: params.message,
|
|
10460
|
+
hint: params.hint,
|
|
10461
|
+
recommendedCommand: params.recommendedCommand,
|
|
10462
|
+
autoFix: {
|
|
10463
|
+
status: "not_attempted",
|
|
10464
|
+
command: null,
|
|
10465
|
+
pid: null,
|
|
10466
|
+
logPath: null,
|
|
10467
|
+
attemptedAt: null,
|
|
10468
|
+
failureMessage: null
|
|
10469
|
+
}
|
|
10470
|
+
};
|
|
10471
|
+
}
|
|
10472
|
+
|
|
10473
|
+
// src/hook-diagnostics.ts
|
|
10474
|
+
var import_node_crypto2 = require("crypto");
|
|
10475
|
+
var import_promises21 = __toESM(require("fs/promises"), 1);
|
|
10476
|
+
var import_node_os6 = __toESM(require("os"), 1);
|
|
10477
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
10478
|
+
|
|
10479
|
+
// src/hook-state.ts
|
|
10480
|
+
var import_promises20 = __toESM(require("fs/promises"), 1);
|
|
10481
|
+
var import_node_os5 = __toESM(require("os"), 1);
|
|
10482
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
10483
|
+
var import_node_crypto = require("crypto");
|
|
10484
|
+
function stateRoot2() {
|
|
10485
|
+
const configured = process.env.REMIX_CLAUDE_PLUGIN_HOOK_STATE_ROOT?.trim();
|
|
10486
|
+
return configured || import_node_path8.default.join(import_node_os5.default.tmpdir(), "remix-claude-plugin-hooks");
|
|
10487
|
+
}
|
|
10488
|
+
function statePath(sessionId) {
|
|
10489
|
+
return import_node_path8.default.join(stateRoot2(), `${sessionId}.json`);
|
|
10490
|
+
}
|
|
10491
|
+
function stateLockPath(sessionId) {
|
|
10492
|
+
return import_node_path8.default.join(stateRoot2(), `${sessionId}.lock`);
|
|
10493
|
+
}
|
|
10494
|
+
function stateLockMetaPath(sessionId) {
|
|
10495
|
+
return import_node_path8.default.join(stateLockPath(sessionId), "owner.json");
|
|
10496
|
+
}
|
|
10497
|
+
async function writeJsonAtomic2(filePath, value) {
|
|
10498
|
+
await import_promises20.default.mkdir(import_node_path8.default.dirname(filePath), { recursive: true });
|
|
10499
|
+
const tmpPath = `${filePath}.tmp-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
10500
|
+
await import_promises20.default.writeFile(tmpPath, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
10501
|
+
await import_promises20.default.rename(tmpPath, filePath);
|
|
10502
|
+
}
|
|
10503
|
+
var STATE_LOCK_WAIT_MS = 2e3;
|
|
10504
|
+
var STATE_LOCK_POLL_MS = 25;
|
|
10505
|
+
var STATE_LOCK_STALE_MS = 3e4;
|
|
10506
|
+
var STATE_LOCK_HEARTBEAT_MS = 5e3;
|
|
10507
|
+
async function sleep2(ms) {
|
|
10508
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
10509
|
+
}
|
|
10510
|
+
async function readStateLockMetadata(sessionId) {
|
|
10511
|
+
const raw = await import_promises20.default.readFile(stateLockMetaPath(sessionId), "utf8").catch(() => null);
|
|
10512
|
+
if (!raw) return null;
|
|
10513
|
+
try {
|
|
10514
|
+
const parsed = JSON.parse(raw);
|
|
10515
|
+
if (typeof parsed.ownerId !== "string" || typeof parsed.pid !== "number" || typeof parsed.createdAt !== "string" || typeof parsed.heartbeatAt !== "string") {
|
|
10516
|
+
return null;
|
|
10517
|
+
}
|
|
10518
|
+
return {
|
|
10519
|
+
ownerId: parsed.ownerId,
|
|
10520
|
+
pid: parsed.pid,
|
|
10521
|
+
createdAt: parsed.createdAt,
|
|
10522
|
+
heartbeatAt: parsed.heartbeatAt
|
|
10523
|
+
};
|
|
10524
|
+
} catch {
|
|
10525
|
+
return null;
|
|
10526
|
+
}
|
|
10527
|
+
}
|
|
10528
|
+
async function writeStateLockMetadata(sessionId, metadata) {
|
|
10529
|
+
await writeJsonAtomic2(stateLockMetaPath(sessionId), metadata);
|
|
10530
|
+
}
|
|
10531
|
+
async function tryRemoveStaleStateLock(sessionId) {
|
|
10532
|
+
const lockPath = stateLockPath(sessionId);
|
|
10533
|
+
const metadata = await readStateLockMetadata(sessionId);
|
|
10534
|
+
const staleByHeartbeat = metadata && Date.now() - new Date(metadata.heartbeatAt).getTime() > STATE_LOCK_STALE_MS;
|
|
10535
|
+
if (staleByHeartbeat) {
|
|
10536
|
+
await import_promises20.default.rm(lockPath, { recursive: true, force: true }).catch(() => void 0);
|
|
10537
|
+
return true;
|
|
10538
|
+
}
|
|
10539
|
+
if (!metadata) {
|
|
10540
|
+
const lockStat = await import_promises20.default.stat(lockPath).catch(() => null);
|
|
10541
|
+
if (lockStat && Date.now() - lockStat.mtimeMs > STATE_LOCK_STALE_MS) {
|
|
10542
|
+
await import_promises20.default.rm(lockPath, { recursive: true, force: true }).catch(() => void 0);
|
|
10543
|
+
return true;
|
|
10544
|
+
}
|
|
10545
|
+
}
|
|
10546
|
+
return false;
|
|
10547
|
+
}
|
|
10548
|
+
async function acquireStateLock(sessionId) {
|
|
10549
|
+
const lockPath = stateLockPath(sessionId);
|
|
10550
|
+
const deadline = Date.now() + STATE_LOCK_WAIT_MS;
|
|
10551
|
+
await import_promises20.default.mkdir(stateRoot2(), { recursive: true });
|
|
10552
|
+
while (true) {
|
|
10553
|
+
try {
|
|
10554
|
+
await import_promises20.default.mkdir(lockPath);
|
|
10555
|
+
const ownerId = (0, import_node_crypto.randomUUID)();
|
|
10556
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10557
|
+
const metadata = {
|
|
10558
|
+
ownerId,
|
|
10559
|
+
pid: process.pid,
|
|
10560
|
+
createdAt,
|
|
10561
|
+
heartbeatAt: createdAt
|
|
10562
|
+
};
|
|
10563
|
+
await writeStateLockMetadata(sessionId, metadata);
|
|
10564
|
+
let released = false;
|
|
10565
|
+
const heartbeat = setInterval(() => {
|
|
10566
|
+
if (released) return;
|
|
10567
|
+
void writeStateLockMetadata(sessionId, {
|
|
10568
|
+
...metadata,
|
|
10569
|
+
heartbeatAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10570
|
+
}).catch(() => void 0);
|
|
10571
|
+
}, STATE_LOCK_HEARTBEAT_MS);
|
|
10572
|
+
heartbeat.unref?.();
|
|
10573
|
+
return async () => {
|
|
10574
|
+
if (released) return;
|
|
10575
|
+
released = true;
|
|
10576
|
+
clearInterval(heartbeat);
|
|
10577
|
+
const currentMetadata = await readStateLockMetadata(sessionId);
|
|
10578
|
+
if (currentMetadata?.ownerId === ownerId) {
|
|
10579
|
+
await import_promises20.default.rm(lockPath, { recursive: true, force: true }).catch(() => void 0);
|
|
10580
|
+
}
|
|
10581
|
+
};
|
|
10582
|
+
} catch (error) {
|
|
10583
|
+
const code = error && typeof error === "object" && "code" in error ? error.code : null;
|
|
10584
|
+
if (code !== "EEXIST") {
|
|
10585
|
+
throw error;
|
|
10586
|
+
}
|
|
10587
|
+
if (await tryRemoveStaleStateLock(sessionId)) {
|
|
10588
|
+
continue;
|
|
10589
|
+
}
|
|
10590
|
+
if (Date.now() >= deadline) {
|
|
10591
|
+
throw new Error(`Timed out acquiring hook state lock for session ${sessionId}.`);
|
|
10592
|
+
}
|
|
10593
|
+
await sleep2(STATE_LOCK_POLL_MS);
|
|
10594
|
+
}
|
|
10595
|
+
}
|
|
10596
|
+
}
|
|
10597
|
+
async function withStateLock(sessionId, fn) {
|
|
10598
|
+
const release = await acquireStateLock(sessionId);
|
|
10599
|
+
try {
|
|
10600
|
+
return await fn();
|
|
10601
|
+
} finally {
|
|
10602
|
+
await release();
|
|
10603
|
+
}
|
|
10604
|
+
}
|
|
10605
|
+
async function savePendingTurnState(state) {
|
|
10606
|
+
await writeJsonAtomic2(statePath(state.sessionId), state);
|
|
10607
|
+
}
|
|
10608
|
+
async function createPendingTurnState(params) {
|
|
10609
|
+
return withStateLock(params.sessionId, async () => {
|
|
10610
|
+
const state = {
|
|
10611
|
+
sessionId: params.sessionId,
|
|
10612
|
+
turnId: (0, import_node_crypto.randomUUID)(),
|
|
10613
|
+
prompt: params.prompt,
|
|
10614
|
+
initialCwd: params.initialCwd?.trim() || null,
|
|
10615
|
+
intent: params.intent,
|
|
10616
|
+
submittedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10617
|
+
consultedMemory: false,
|
|
10618
|
+
touchedRepos: {},
|
|
10619
|
+
turnFailureMessage: null,
|
|
10620
|
+
turnFailureHint: null,
|
|
10621
|
+
turnFailedAt: null
|
|
10622
|
+
};
|
|
10623
|
+
await savePendingTurnState(state);
|
|
10624
|
+
return state;
|
|
10625
|
+
});
|
|
10626
|
+
}
|
|
10627
|
+
|
|
10628
|
+
// package.json
|
|
10629
|
+
var package_default = {
|
|
10630
|
+
name: "@remixhq/claude-plugin",
|
|
10631
|
+
version: "0.1.24",
|
|
10632
|
+
description: "Claude Code plugin for Remix collaboration workflows",
|
|
10633
|
+
homepage: "https://github.com/RemixDotOne/remix-claude-plugin",
|
|
10634
|
+
license: "MIT",
|
|
10635
|
+
repository: {
|
|
10636
|
+
type: "git",
|
|
10637
|
+
url: "https://github.com/RemixDotOne/remix-claude-plugin.git"
|
|
10638
|
+
},
|
|
10639
|
+
type: "module",
|
|
10640
|
+
engines: {
|
|
10641
|
+
node: ">=20"
|
|
10642
|
+
},
|
|
10643
|
+
publishConfig: {
|
|
10644
|
+
access: "public"
|
|
10645
|
+
},
|
|
10646
|
+
files: [
|
|
10647
|
+
"dist",
|
|
10648
|
+
".claude-plugin/plugin.json",
|
|
10649
|
+
".mcp.json",
|
|
10650
|
+
"skills",
|
|
10651
|
+
"hooks",
|
|
10652
|
+
"agents"
|
|
10653
|
+
],
|
|
10654
|
+
exports: {
|
|
10655
|
+
".": {
|
|
10656
|
+
types: "./dist/index.d.ts",
|
|
10657
|
+
import: "./dist/index.js"
|
|
10658
|
+
}
|
|
10659
|
+
},
|
|
10660
|
+
scripts: {
|
|
10661
|
+
build: "tsup",
|
|
10662
|
+
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);"`,
|
|
10663
|
+
dev: "tsx src/mcp-server.ts",
|
|
10664
|
+
typecheck: "tsc -p tsconfig.json --noEmit",
|
|
10665
|
+
test: "node --import tsx --test 'src/**/*.test.ts'",
|
|
10666
|
+
prepack: "npm run build"
|
|
10667
|
+
},
|
|
10668
|
+
dependencies: {
|
|
10669
|
+
"@remixhq/core": "^0.1.19",
|
|
10670
|
+
"@remixhq/mcp": "^0.1.19"
|
|
10671
|
+
},
|
|
10672
|
+
devDependencies: {
|
|
10673
|
+
"@types/node": "^25.4.0",
|
|
10674
|
+
tsup: "^8.5.1",
|
|
10675
|
+
tsx: "^4.21.0",
|
|
10676
|
+
typescript: "^5.9.3"
|
|
10677
|
+
}
|
|
10678
|
+
};
|
|
10679
|
+
|
|
10680
|
+
// src/metadata.ts
|
|
10681
|
+
var pluginMetadata = {
|
|
10682
|
+
name: package_default.name,
|
|
10683
|
+
version: package_default.version,
|
|
10684
|
+
description: package_default.description,
|
|
10685
|
+
pluginId: "remix",
|
|
10686
|
+
agentName: "remix-collab"
|
|
10687
|
+
};
|
|
10688
|
+
|
|
10689
|
+
// src/hook-diagnostics.ts
|
|
10690
|
+
var MAX_LOG_BYTES = 512 * 1024;
|
|
10691
|
+
function resolveClaudeRoot() {
|
|
10692
|
+
const configured = process.env.CLAUDE_CONFIG_DIR?.trim();
|
|
10693
|
+
return configured || import_node_path9.default.join(import_node_os6.default.homedir(), ".claude");
|
|
10694
|
+
}
|
|
10695
|
+
function resolvePluginDataDirName() {
|
|
10696
|
+
return `${pluginMetadata.pluginId}-${pluginMetadata.pluginId}`;
|
|
10697
|
+
}
|
|
10698
|
+
function getHookDiagnosticsDirPath() {
|
|
10699
|
+
const configured = process.env.REMIX_CLAUDE_PLUGIN_HOOK_DIAGNOSTICS_DIR?.trim();
|
|
10700
|
+
return configured || import_node_path9.default.join(resolveClaudeRoot(), "plugins", "data", resolvePluginDataDirName());
|
|
10701
|
+
}
|
|
10702
|
+
function getHookDiagnosticsLogPath() {
|
|
10703
|
+
return import_node_path9.default.join(getHookDiagnosticsDirPath(), "hooks.ndjson");
|
|
10704
|
+
}
|
|
10705
|
+
function toFieldValue(value) {
|
|
10706
|
+
if (value === null) return null;
|
|
10707
|
+
if (typeof value === "string") return value;
|
|
10708
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
10709
|
+
if (typeof value === "boolean") return value;
|
|
10710
|
+
return void 0;
|
|
10711
|
+
}
|
|
10712
|
+
function normalizeFields(fields) {
|
|
10713
|
+
if (!fields) return {};
|
|
10714
|
+
const normalizedEntries = Object.entries(fields).map(([key, value]) => {
|
|
10715
|
+
const normalized = toFieldValue(value);
|
|
10716
|
+
return normalized === void 0 ? null : [key, normalized];
|
|
10717
|
+
}).filter((entry) => entry !== null);
|
|
10718
|
+
return Object.fromEntries(normalizedEntries);
|
|
10719
|
+
}
|
|
10720
|
+
async function rotateLogIfNeeded(logPath) {
|
|
10721
|
+
const stat = await import_promises21.default.stat(logPath).catch(() => null);
|
|
10722
|
+
if (!stat || stat.size < MAX_LOG_BYTES) {
|
|
10723
|
+
return;
|
|
10724
|
+
}
|
|
10725
|
+
const rotatedPath = `${logPath}.1`;
|
|
10726
|
+
await import_promises21.default.rm(rotatedPath, { force: true }).catch(() => void 0);
|
|
10727
|
+
await import_promises21.default.rename(logPath, rotatedPath).catch(() => void 0);
|
|
10728
|
+
}
|
|
10729
|
+
function summarizeText(value) {
|
|
10730
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
10731
|
+
return {
|
|
10732
|
+
present: false,
|
|
10733
|
+
length: 0,
|
|
10734
|
+
sha256Prefix: null
|
|
10735
|
+
};
|
|
10736
|
+
}
|
|
10737
|
+
const trimmed = value.trim();
|
|
10738
|
+
return {
|
|
10739
|
+
present: true,
|
|
10740
|
+
length: trimmed.length,
|
|
10741
|
+
sha256Prefix: (0, import_node_crypto2.createHash)("sha256").update(trimmed).digest("hex").slice(0, 12)
|
|
10742
|
+
};
|
|
10743
|
+
}
|
|
10744
|
+
async function appendHookDiagnosticsEvent(params) {
|
|
10745
|
+
try {
|
|
10746
|
+
const logPath = getHookDiagnosticsLogPath();
|
|
10747
|
+
await import_promises21.default.mkdir(import_node_path9.default.dirname(logPath), { recursive: true });
|
|
10748
|
+
await rotateLogIfNeeded(logPath);
|
|
10749
|
+
const event = {
|
|
10750
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10751
|
+
hook: params.hook,
|
|
10752
|
+
pluginVersion: pluginMetadata.version,
|
|
10753
|
+
pid: process.pid,
|
|
10754
|
+
sessionId: params.sessionId?.trim() || null,
|
|
10755
|
+
turnId: params.turnId?.trim() || null,
|
|
10756
|
+
stage: params.stage.trim(),
|
|
10757
|
+
result: params.result,
|
|
10758
|
+
reason: params.reason?.trim() || null,
|
|
10759
|
+
toolName: params.toolName?.trim() || null,
|
|
10760
|
+
repoRoot: params.repoRoot?.trim() || null,
|
|
10761
|
+
message: params.message?.trim() || null,
|
|
10762
|
+
fields: normalizeFields(params.fields)
|
|
10763
|
+
};
|
|
10764
|
+
await import_promises21.default.appendFile(logPath, `${JSON.stringify(event)}
|
|
10765
|
+
`, "utf8");
|
|
10766
|
+
} catch {
|
|
10767
|
+
}
|
|
10768
|
+
}
|
|
10769
|
+
|
|
10770
|
+
// src/auto-fix-dispatcher.ts
|
|
10771
|
+
var AUTO_FIX_COMMAND = {
|
|
10772
|
+
// Already auto-spawned by hook-user-prompt's branch-init path, but we
|
|
10773
|
+
// include it here too so a finalize-time failure (e.g. binding got
|
|
10774
|
+
// deleted between init and the next finalize) also self-heals.
|
|
10775
|
+
branch_binding_missing: ["collab", "init"],
|
|
10776
|
+
// Local revision baseline is missing. Init seeds the branch/lane baseline
|
|
10777
|
+
// without requiring the user to know about the recording internals.
|
|
10778
|
+
baseline_missing: ["collab", "init"],
|
|
10779
|
+
// Server moved ahead. `collab sync` is fast-forward-safe by default;
|
|
10780
|
+
// it refuses non-FF on its own, so we don't need to gate here.
|
|
10781
|
+
pull_required: ["collab", "sync"]
|
|
10782
|
+
};
|
|
10783
|
+
var RECOMMENDED_USER_COMMAND = {
|
|
10784
|
+
not_bound: "remix collab init",
|
|
10785
|
+
branch_binding_missing: "remix collab init",
|
|
10786
|
+
family_ambiguous: "remix collab status",
|
|
10787
|
+
metadata_conflict: "remix collab status",
|
|
10788
|
+
branch_mismatch: "remix collab status",
|
|
10789
|
+
missing_head: "remix collab status",
|
|
10790
|
+
remote_error: "remix collab status",
|
|
10791
|
+
pull_required: "remix collab sync",
|
|
10792
|
+
baseline_missing: "remix collab init"
|
|
10793
|
+
};
|
|
10794
|
+
var SPAWN_LOCK_REL = (cmdSlug) => import_node_path10.default.join(".remix", `.${cmdSlug}-spawning`);
|
|
10795
|
+
var SPAWN_LOG_REL = (cmdSlug) => import_node_path10.default.join(".remix", `${cmdSlug}.log`);
|
|
10796
|
+
var SPAWN_THROTTLE_MS = 5 * 60 * 1e3;
|
|
10797
|
+
function commandSlug(args) {
|
|
10798
|
+
return args.join("-").replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
10799
|
+
}
|
|
10800
|
+
function spawnFixDetached(repoRoot, args) {
|
|
10801
|
+
const slug = commandSlug(args);
|
|
10802
|
+
const command = `remix ${args.join(" ")}`;
|
|
10803
|
+
const remixDir = import_node_path10.default.join(repoRoot, ".remix");
|
|
10804
|
+
const lockPath = import_node_path10.default.join(repoRoot, SPAWN_LOCK_REL(slug));
|
|
10805
|
+
const logPath = import_node_path10.default.join(repoRoot, SPAWN_LOG_REL(slug));
|
|
10806
|
+
try {
|
|
10807
|
+
if ((0, import_node_fs6.existsSync)(lockPath)) {
|
|
10808
|
+
const ageMs = Date.now() - (0, import_node_fs6.statSync)(lockPath).mtimeMs;
|
|
10809
|
+
if (ageMs < SPAWN_THROTTLE_MS) {
|
|
10810
|
+
return { kind: "spawn_throttled", command, reason: "spawn_lock_held" };
|
|
10811
|
+
}
|
|
10812
|
+
}
|
|
10813
|
+
} catch {
|
|
10814
|
+
}
|
|
10815
|
+
try {
|
|
10816
|
+
(0, import_node_fs6.mkdirSync)(remixDir, { recursive: true });
|
|
10817
|
+
} catch {
|
|
10818
|
+
}
|
|
10819
|
+
let out;
|
|
10820
|
+
let err;
|
|
10821
|
+
try {
|
|
10822
|
+
out = (0, import_node_fs6.openSync)(logPath, "a");
|
|
10823
|
+
err = (0, import_node_fs6.openSync)(logPath, "a");
|
|
10824
|
+
} catch (logErr) {
|
|
10825
|
+
return {
|
|
10826
|
+
kind: "spawn_failed",
|
|
10827
|
+
command,
|
|
10828
|
+
reason: "log_open_failed",
|
|
10829
|
+
message: logErr instanceof Error ? logErr.message : String(logErr)
|
|
10830
|
+
};
|
|
10831
|
+
}
|
|
10832
|
+
try {
|
|
10833
|
+
const child = (0, import_node_child_process6.spawn)("remix", [...args], {
|
|
10834
|
+
cwd: repoRoot,
|
|
10835
|
+
detached: true,
|
|
10836
|
+
stdio: ["ignore", out, err],
|
|
10837
|
+
env: { ...process.env, REMIX_AUTO_FIX_SPAWN: "1" }
|
|
10838
|
+
});
|
|
10839
|
+
child.unref();
|
|
10840
|
+
try {
|
|
10841
|
+
(0, import_node_fs6.writeFileSync)(lockPath, String(child.pid ?? ""), "utf8");
|
|
10842
|
+
(0, import_node_fs6.utimesSync)(lockPath, /* @__PURE__ */ new Date(), /* @__PURE__ */ new Date());
|
|
10843
|
+
} catch {
|
|
10844
|
+
}
|
|
10845
|
+
return { kind: "spawned", command, pid: child.pid, logPath };
|
|
10846
|
+
} catch (spawnErr) {
|
|
10847
|
+
return {
|
|
10848
|
+
kind: "spawn_failed",
|
|
10849
|
+
command,
|
|
10850
|
+
reason: "spawn_failed",
|
|
10851
|
+
message: spawnErr instanceof Error ? spawnErr.message : String(spawnErr)
|
|
10852
|
+
};
|
|
10853
|
+
}
|
|
10854
|
+
}
|
|
10855
|
+
async function dispatchFinalizeFailure(input) {
|
|
10856
|
+
const recommendedCommand = input.preflightCode ? RECOMMENDED_USER_COMMAND[input.preflightCode] ?? null : null;
|
|
10857
|
+
const marker = buildFreshFailureMarker({
|
|
10858
|
+
repoRoot: input.repoRoot,
|
|
10859
|
+
preflightCode: input.preflightCode,
|
|
10860
|
+
message: input.message,
|
|
10861
|
+
hint: input.hint,
|
|
10862
|
+
recommendedCommand
|
|
10863
|
+
});
|
|
10864
|
+
let outcome;
|
|
10865
|
+
const autoFixArgs = input.preflightCode ? AUTO_FIX_COMMAND[input.preflightCode] : void 0;
|
|
10866
|
+
if (!autoFixArgs) {
|
|
10867
|
+
outcome = {
|
|
10868
|
+
kind: "warn_only",
|
|
10869
|
+
reason: input.preflightCode ? "no_auto_fix_for_code" : "unknown_code"
|
|
10870
|
+
};
|
|
10871
|
+
} else {
|
|
10872
|
+
outcome = spawnFixDetached(input.repoRoot, autoFixArgs);
|
|
10873
|
+
marker.autoFix = mergeOutcomeIntoMarker(marker.autoFix, outcome);
|
|
10874
|
+
}
|
|
10875
|
+
try {
|
|
10876
|
+
await writeFinalizeFailureMarker(marker);
|
|
10877
|
+
} catch (writeErr) {
|
|
10878
|
+
await appendHookDiagnosticsEvent({
|
|
10879
|
+
hook: input.hook,
|
|
10880
|
+
sessionId: input.sessionId,
|
|
10881
|
+
turnId: input.turnId ?? void 0,
|
|
10882
|
+
stage: "finalize_failure_marker_write_failed",
|
|
10883
|
+
result: "error",
|
|
10884
|
+
reason: "exception",
|
|
10885
|
+
repoRoot: input.repoRoot,
|
|
10886
|
+
message: writeErr instanceof Error ? writeErr.message : String(writeErr)
|
|
10887
|
+
});
|
|
10888
|
+
}
|
|
10889
|
+
await appendHookDiagnosticsEvent({
|
|
10890
|
+
hook: input.hook,
|
|
10891
|
+
sessionId: input.sessionId,
|
|
10892
|
+
turnId: input.turnId ?? void 0,
|
|
10893
|
+
stage: "auto_fix_dispatched",
|
|
10894
|
+
result: outcome.kind === "spawned" ? "success" : outcome.kind === "warn_only" ? "info" : "error",
|
|
10895
|
+
reason: outcome.kind,
|
|
10896
|
+
repoRoot: input.repoRoot,
|
|
10897
|
+
fields: {
|
|
10898
|
+
preflightCode: input.preflightCode,
|
|
10899
|
+
command: "command" in outcome ? outcome.command : null,
|
|
10900
|
+
pid: outcome.kind === "spawned" ? outcome.pid ?? null : null,
|
|
10901
|
+
logPath: outcome.kind === "spawned" ? outcome.logPath : null,
|
|
10902
|
+
recommendedCommand
|
|
10903
|
+
},
|
|
10904
|
+
message: outcome.kind === "spawn_failed" ? outcome.message : null
|
|
10905
|
+
});
|
|
10906
|
+
return outcome;
|
|
10907
|
+
}
|
|
10908
|
+
function mergeOutcomeIntoMarker(existing, outcome) {
|
|
10909
|
+
if (outcome.kind === "spawned") {
|
|
10910
|
+
return {
|
|
10911
|
+
status: "in_progress",
|
|
10912
|
+
command: outcome.command,
|
|
10913
|
+
pid: outcome.pid ?? null,
|
|
10914
|
+
logPath: outcome.logPath,
|
|
10915
|
+
attemptedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10916
|
+
failureMessage: null
|
|
10917
|
+
};
|
|
10918
|
+
}
|
|
10919
|
+
if (outcome.kind === "spawn_throttled") {
|
|
10920
|
+
return {
|
|
10921
|
+
status: "in_progress",
|
|
10922
|
+
command: outcome.command,
|
|
10923
|
+
pid: existing.pid,
|
|
10924
|
+
logPath: existing.logPath,
|
|
10925
|
+
attemptedAt: existing.attemptedAt,
|
|
10926
|
+
failureMessage: null
|
|
10927
|
+
};
|
|
10928
|
+
}
|
|
10929
|
+
if (outcome.kind === "spawn_failed") {
|
|
10930
|
+
return {
|
|
10931
|
+
status: "spawn_failed",
|
|
10932
|
+
command: outcome.command,
|
|
10933
|
+
pid: null,
|
|
10934
|
+
logPath: null,
|
|
10935
|
+
attemptedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10936
|
+
failureMessage: outcome.message
|
|
10937
|
+
};
|
|
10938
|
+
}
|
|
10939
|
+
return existing;
|
|
10940
|
+
}
|
|
10234
10941
|
|
|
10235
|
-
// node_modules/@remixhq/core/dist/chunk-
|
|
10942
|
+
// node_modules/@remixhq/core/dist/chunk-C2FOZ3O7.js
|
|
10236
10943
|
async function readJsonSafe(res) {
|
|
10237
10944
|
const ct = res.headers.get("content-type") ?? "";
|
|
10238
10945
|
if (!ct.toLowerCase().includes("application/json")) return null;
|
|
@@ -10245,8 +10952,13 @@ async function readJsonSafe(res) {
|
|
|
10245
10952
|
function createApiClient(config, opts) {
|
|
10246
10953
|
const apiKey = (opts?.apiKey ?? "").trim();
|
|
10247
10954
|
const tokenProvider = opts?.tokenProvider;
|
|
10955
|
+
const defaultTimeoutMs = typeof opts?.defaultRequestTimeoutMs === "number" && opts.defaultRequestTimeoutMs > 0 ? opts.defaultRequestTimeoutMs : null;
|
|
10248
10956
|
const CLIENT_KEY_HEADER = "x-comerge-api-key";
|
|
10249
|
-
|
|
10957
|
+
function makeTimeoutSignal(timeoutMs) {
|
|
10958
|
+
const ms = typeof timeoutMs === "number" && timeoutMs > 0 ? timeoutMs : defaultTimeoutMs;
|
|
10959
|
+
return ms != null ? AbortSignal.timeout(ms) : void 0;
|
|
10960
|
+
}
|
|
10961
|
+
async function request(path17, init, opts2) {
|
|
10250
10962
|
if (!tokenProvider) {
|
|
10251
10963
|
throw new RemixError("API client is missing a token provider.", {
|
|
10252
10964
|
exitCode: 1,
|
|
@@ -10254,9 +10966,10 @@ function createApiClient(config, opts) {
|
|
|
10254
10966
|
});
|
|
10255
10967
|
}
|
|
10256
10968
|
const auth = await tokenProvider();
|
|
10257
|
-
const url = new URL(
|
|
10969
|
+
const url = new URL(path17, config.apiUrl).toString();
|
|
10258
10970
|
const doFetch = async (bearer) => fetch(url, {
|
|
10259
10971
|
...init,
|
|
10972
|
+
signal: makeTimeoutSignal(opts2?.timeoutMs),
|
|
10260
10973
|
headers: {
|
|
10261
10974
|
Accept: "application/json",
|
|
10262
10975
|
"Content-Type": "application/json",
|
|
@@ -10273,12 +10986,16 @@ function createApiClient(config, opts) {
|
|
|
10273
10986
|
if (!res.ok) {
|
|
10274
10987
|
const body = await readJsonSafe(res);
|
|
10275
10988
|
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, {
|
|
10989
|
+
throw new RemixError(msg, {
|
|
10990
|
+
exitCode: 1,
|
|
10991
|
+
hint: body ? JSON.stringify(body, null, 2) : null,
|
|
10992
|
+
statusCode: res.status
|
|
10993
|
+
});
|
|
10277
10994
|
}
|
|
10278
10995
|
const json = await readJsonSafe(res);
|
|
10279
10996
|
return json ?? null;
|
|
10280
10997
|
}
|
|
10281
|
-
async function requestBinary(
|
|
10998
|
+
async function requestBinary(path17, init, opts2) {
|
|
10282
10999
|
if (!tokenProvider) {
|
|
10283
11000
|
throw new RemixError("API client is missing a token provider.", {
|
|
10284
11001
|
exitCode: 1,
|
|
@@ -10286,9 +11003,10 @@ function createApiClient(config, opts) {
|
|
|
10286
11003
|
});
|
|
10287
11004
|
}
|
|
10288
11005
|
const auth = await tokenProvider();
|
|
10289
|
-
const url = new URL(
|
|
11006
|
+
const url = new URL(path17, config.apiUrl).toString();
|
|
10290
11007
|
const doFetch = async (bearer) => fetch(url, {
|
|
10291
11008
|
...init,
|
|
11009
|
+
signal: makeTimeoutSignal(opts2?.timeoutMs),
|
|
10292
11010
|
headers: {
|
|
10293
11011
|
Accept: "*/*",
|
|
10294
11012
|
...init?.headers ?? {},
|
|
@@ -10304,7 +11022,11 @@ function createApiClient(config, opts) {
|
|
|
10304
11022
|
if (!res.ok) {
|
|
10305
11023
|
const body = await readJsonSafe(res);
|
|
10306
11024
|
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, {
|
|
11025
|
+
throw new RemixError(msg, {
|
|
11026
|
+
exitCode: 1,
|
|
11027
|
+
hint: body ? JSON.stringify(body, null, 2) : null,
|
|
11028
|
+
statusCode: res.status
|
|
11029
|
+
});
|
|
10308
11030
|
}
|
|
10309
11031
|
const contentDisposition = res.headers.get("content-disposition") ?? "";
|
|
10310
11032
|
const fileNameMatch = contentDisposition.match(/filename=\"([^\"]+)\"/i);
|
|
@@ -10411,6 +11133,14 @@ function createApiClient(config, opts) {
|
|
|
10411
11133
|
method: "POST",
|
|
10412
11134
|
body: JSON.stringify(payload)
|
|
10413
11135
|
}),
|
|
11136
|
+
listChangeSteps: (appId, params) => {
|
|
11137
|
+
const qs = new URLSearchParams();
|
|
11138
|
+
if (params?.limit !== void 0) qs.set("limit", String(params.limit));
|
|
11139
|
+
if (params?.offset !== void 0) qs.set("offset", String(params.offset));
|
|
11140
|
+
if (params?.idempotencyKey) qs.set("idempotencyKey", params.idempotencyKey);
|
|
11141
|
+
const suffix = qs.toString() ? `?${qs.toString()}` : "";
|
|
11142
|
+
return request(`/v1/apps/${encodeURIComponent(appId)}/change-steps${suffix}`, { method: "GET" });
|
|
11143
|
+
},
|
|
10414
11144
|
createCollabTurn: (appId, payload) => request(`/v1/apps/${encodeURIComponent(appId)}/collab-turns`, {
|
|
10415
11145
|
method: "POST",
|
|
10416
11146
|
body: JSON.stringify(payload)
|
|
@@ -11136,8 +11866,8 @@ function getErrorMap() {
|
|
|
11136
11866
|
|
|
11137
11867
|
// node_modules/zod/v3/helpers/parseUtil.js
|
|
11138
11868
|
var makeIssue = (params) => {
|
|
11139
|
-
const { data, path:
|
|
11140
|
-
const fullPath = [...
|
|
11869
|
+
const { data, path: path17, errorMaps, issueData } = params;
|
|
11870
|
+
const fullPath = [...path17, ...issueData.path || []];
|
|
11141
11871
|
const fullIssue = {
|
|
11142
11872
|
...issueData,
|
|
11143
11873
|
path: fullPath
|
|
@@ -11253,11 +11983,11 @@ var errorUtil;
|
|
|
11253
11983
|
|
|
11254
11984
|
// node_modules/zod/v3/types.js
|
|
11255
11985
|
var ParseInputLazyPath = class {
|
|
11256
|
-
constructor(parent, value,
|
|
11986
|
+
constructor(parent, value, path17, key) {
|
|
11257
11987
|
this._cachedPath = [];
|
|
11258
11988
|
this.parent = parent;
|
|
11259
11989
|
this.data = value;
|
|
11260
|
-
this._path =
|
|
11990
|
+
this._path = path17;
|
|
11261
11991
|
this._key = key;
|
|
11262
11992
|
}
|
|
11263
11993
|
get path() {
|
|
@@ -14699,8 +15429,8 @@ var coerce = {
|
|
|
14699
15429
|
};
|
|
14700
15430
|
var NEVER = INVALID;
|
|
14701
15431
|
|
|
14702
|
-
// node_modules/@remixhq/core/dist/chunk-
|
|
14703
|
-
var
|
|
15432
|
+
// node_modules/@remixhq/core/dist/chunk-XETDXVGM.js
|
|
15433
|
+
var import_promises22 = __toESM(require("fs/promises"), 1);
|
|
14704
15434
|
var import_os3 = __toESM(require("os"), 1);
|
|
14705
15435
|
var import_path7 = __toESM(require("path"), 1);
|
|
14706
15436
|
|
|
@@ -15108,7 +15838,7 @@ var PostgrestError = class extends Error {
|
|
|
15108
15838
|
};
|
|
15109
15839
|
}
|
|
15110
15840
|
};
|
|
15111
|
-
function
|
|
15841
|
+
function sleep3(ms, signal) {
|
|
15112
15842
|
return new Promise((resolve) => {
|
|
15113
15843
|
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
15114
15844
|
resolve();
|
|
@@ -15304,7 +16034,7 @@ var PostgrestBuilder = class {
|
|
|
15304
16034
|
if (_this.retryEnabled && attemptCount < DEFAULT_MAX_RETRIES) {
|
|
15305
16035
|
const delay = getRetryDelay(attemptCount);
|
|
15306
16036
|
attemptCount++;
|
|
15307
|
-
await
|
|
16037
|
+
await sleep3(delay, _this.signal);
|
|
15308
16038
|
continue;
|
|
15309
16039
|
}
|
|
15310
16040
|
throw fetchError;
|
|
@@ -15315,7 +16045,7 @@ var PostgrestBuilder = class {
|
|
|
15315
16045
|
const delay = retryAfterHeader !== null ? Math.max(0, parseInt(retryAfterHeader, 10) || 0) * 1e3 : getRetryDelay(attemptCount);
|
|
15316
16046
|
await res$1.text();
|
|
15317
16047
|
attemptCount++;
|
|
15318
|
-
await
|
|
16048
|
+
await sleep3(delay, _this.signal);
|
|
15319
16049
|
continue;
|
|
15320
16050
|
}
|
|
15321
16051
|
return await _this.processResponse(res$1);
|
|
@@ -23805,8 +24535,8 @@ var IcebergError = class extends Error {
|
|
|
23805
24535
|
return this.status === 419;
|
|
23806
24536
|
}
|
|
23807
24537
|
};
|
|
23808
|
-
function buildUrl(baseUrl,
|
|
23809
|
-
const url = new URL(
|
|
24538
|
+
function buildUrl(baseUrl, path17, query) {
|
|
24539
|
+
const url = new URL(path17, baseUrl);
|
|
23810
24540
|
if (query) {
|
|
23811
24541
|
for (const [key, value] of Object.entries(query)) {
|
|
23812
24542
|
if (value !== void 0) {
|
|
@@ -23836,12 +24566,12 @@ function createFetchClient(options) {
|
|
|
23836
24566
|
return {
|
|
23837
24567
|
async request({
|
|
23838
24568
|
method,
|
|
23839
|
-
path:
|
|
24569
|
+
path: path17,
|
|
23840
24570
|
query,
|
|
23841
24571
|
body,
|
|
23842
24572
|
headers
|
|
23843
24573
|
}) {
|
|
23844
|
-
const url = buildUrl(options.baseUrl,
|
|
24574
|
+
const url = buildUrl(options.baseUrl, path17, query);
|
|
23845
24575
|
const authHeaders = await buildAuthHeaders(options.auth);
|
|
23846
24576
|
const res = await fetchFn(url, {
|
|
23847
24577
|
method,
|
|
@@ -24679,7 +25409,7 @@ var StorageFileApi = class extends BaseApiClient {
|
|
|
24679
25409
|
* @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
25410
|
* @param fileBody The body of the file to be stored in the bucket.
|
|
24681
25411
|
*/
|
|
24682
|
-
async uploadOrUpdate(method,
|
|
25412
|
+
async uploadOrUpdate(method, path17, fileBody, fileOptions) {
|
|
24683
25413
|
var _this = this;
|
|
24684
25414
|
return _this.handleOperation(async () => {
|
|
24685
25415
|
let body;
|
|
@@ -24703,7 +25433,7 @@ var StorageFileApi = class extends BaseApiClient {
|
|
|
24703
25433
|
if ((typeof ReadableStream !== "undefined" && body instanceof ReadableStream || body && typeof body === "object" && "pipe" in body && typeof body.pipe === "function") && !options.duplex) options.duplex = "half";
|
|
24704
25434
|
}
|
|
24705
25435
|
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(
|
|
25436
|
+
const cleanPath = _this._removeEmptyFolders(path17);
|
|
24707
25437
|
const _path = _this._getFinalPath(cleanPath);
|
|
24708
25438
|
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
25439
|
return {
|
|
@@ -24764,8 +25494,8 @@ var StorageFileApi = class extends BaseApiClient {
|
|
|
24764
25494
|
* - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
|
|
24765
25495
|
* - 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
25496
|
*/
|
|
24767
|
-
async upload(
|
|
24768
|
-
return this.uploadOrUpdate("POST",
|
|
25497
|
+
async upload(path17, fileBody, fileOptions) {
|
|
25498
|
+
return this.uploadOrUpdate("POST", path17, fileBody, fileOptions);
|
|
24769
25499
|
}
|
|
24770
25500
|
/**
|
|
24771
25501
|
* Upload a file with a token generated from `createSignedUploadUrl`.
|
|
@@ -24804,9 +25534,9 @@ var StorageFileApi = class extends BaseApiClient {
|
|
|
24804
25534
|
* - `objects` table permissions: none
|
|
24805
25535
|
* - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
|
|
24806
25536
|
*/
|
|
24807
|
-
async uploadToSignedUrl(
|
|
25537
|
+
async uploadToSignedUrl(path17, token, fileBody, fileOptions) {
|
|
24808
25538
|
var _this3 = this;
|
|
24809
|
-
const cleanPath = _this3._removeEmptyFolders(
|
|
25539
|
+
const cleanPath = _this3._removeEmptyFolders(path17);
|
|
24810
25540
|
const _path = _this3._getFinalPath(cleanPath);
|
|
24811
25541
|
const url = new URL(_this3.url + `/object/upload/sign/${_path}`);
|
|
24812
25542
|
url.searchParams.set("token", token);
|
|
@@ -24868,10 +25598,10 @@ var StorageFileApi = class extends BaseApiClient {
|
|
|
24868
25598
|
* - `objects` table permissions: `insert`
|
|
24869
25599
|
* - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
|
|
24870
25600
|
*/
|
|
24871
|
-
async createSignedUploadUrl(
|
|
25601
|
+
async createSignedUploadUrl(path17, options) {
|
|
24872
25602
|
var _this4 = this;
|
|
24873
25603
|
return _this4.handleOperation(async () => {
|
|
24874
|
-
let _path = _this4._getFinalPath(
|
|
25604
|
+
let _path = _this4._getFinalPath(path17);
|
|
24875
25605
|
const headers = _objectSpread22({}, _this4.headers);
|
|
24876
25606
|
if (options === null || options === void 0 ? void 0 : options.upsert) headers["x-upsert"] = "true";
|
|
24877
25607
|
const data = await post(_this4.fetch, `${_this4.url}/object/upload/sign/${_path}`, {}, { headers });
|
|
@@ -24880,7 +25610,7 @@ var StorageFileApi = class extends BaseApiClient {
|
|
|
24880
25610
|
if (!token) throw new StorageError("No token returned by API");
|
|
24881
25611
|
return {
|
|
24882
25612
|
signedUrl: url.toString(),
|
|
24883
|
-
path:
|
|
25613
|
+
path: path17,
|
|
24884
25614
|
token
|
|
24885
25615
|
};
|
|
24886
25616
|
});
|
|
@@ -24936,8 +25666,8 @@ var StorageFileApi = class extends BaseApiClient {
|
|
|
24936
25666
|
* - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
|
|
24937
25667
|
* - 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
25668
|
*/
|
|
24939
|
-
async update(
|
|
24940
|
-
return this.uploadOrUpdate("PUT",
|
|
25669
|
+
async update(path17, fileBody, fileOptions) {
|
|
25670
|
+
return this.uploadOrUpdate("PUT", path17, fileBody, fileOptions);
|
|
24941
25671
|
}
|
|
24942
25672
|
/**
|
|
24943
25673
|
* Moves an existing file to a new path in the same bucket.
|
|
@@ -25085,10 +25815,10 @@ var StorageFileApi = class extends BaseApiClient {
|
|
|
25085
25815
|
* - `objects` table permissions: `select`
|
|
25086
25816
|
* - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
|
|
25087
25817
|
*/
|
|
25088
|
-
async createSignedUrl(
|
|
25818
|
+
async createSignedUrl(path17, expiresIn, options) {
|
|
25089
25819
|
var _this8 = this;
|
|
25090
25820
|
return _this8.handleOperation(async () => {
|
|
25091
|
-
let _path = _this8._getFinalPath(
|
|
25821
|
+
let _path = _this8._getFinalPath(path17);
|
|
25092
25822
|
const hasTransform = typeof (options === null || options === void 0 ? void 0 : options.transform) === "object" && options.transform !== null && Object.keys(options.transform).length > 0;
|
|
25093
25823
|
let data = await post(_this8.fetch, `${_this8.url}/object/sign/${_path}`, _objectSpread22({ expiresIn }, hasTransform ? { transform: options.transform } : {}), { headers: _this8.headers });
|
|
25094
25824
|
const query = new URLSearchParams();
|
|
@@ -25222,13 +25952,13 @@ var StorageFileApi = class extends BaseApiClient {
|
|
|
25222
25952
|
* - `objects` table permissions: `select`
|
|
25223
25953
|
* - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
|
|
25224
25954
|
*/
|
|
25225
|
-
download(
|
|
25955
|
+
download(path17, options, parameters) {
|
|
25226
25956
|
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
25957
|
const query = new URLSearchParams();
|
|
25228
25958
|
if (options === null || options === void 0 ? void 0 : options.transform) this.applyTransformOptsToQuery(query, options.transform);
|
|
25229
25959
|
if ((options === null || options === void 0 ? void 0 : options.cacheNonce) != null) query.set("cacheNonce", String(options.cacheNonce));
|
|
25230
25960
|
const queryString = query.toString();
|
|
25231
|
-
const _path = this._getFinalPath(
|
|
25961
|
+
const _path = this._getFinalPath(path17);
|
|
25232
25962
|
const downloadFn = () => get(this.fetch, `${this.url}/${renderPath}/${_path}${queryString ? `?${queryString}` : ""}`, {
|
|
25233
25963
|
headers: this.headers,
|
|
25234
25964
|
noResolveJson: true
|
|
@@ -25258,9 +25988,9 @@ var StorageFileApi = class extends BaseApiClient {
|
|
|
25258
25988
|
* }
|
|
25259
25989
|
* ```
|
|
25260
25990
|
*/
|
|
25261
|
-
async info(
|
|
25991
|
+
async info(path17) {
|
|
25262
25992
|
var _this10 = this;
|
|
25263
|
-
const _path = _this10._getFinalPath(
|
|
25993
|
+
const _path = _this10._getFinalPath(path17);
|
|
25264
25994
|
return _this10.handleOperation(async () => {
|
|
25265
25995
|
return recursiveToCamel(await get(_this10.fetch, `${_this10.url}/object/info/${_path}`, { headers: _this10.headers }));
|
|
25266
25996
|
});
|
|
@@ -25280,9 +26010,9 @@ var StorageFileApi = class extends BaseApiClient {
|
|
|
25280
26010
|
* .exists('folder/avatar1.png')
|
|
25281
26011
|
* ```
|
|
25282
26012
|
*/
|
|
25283
|
-
async exists(
|
|
26013
|
+
async exists(path17) {
|
|
25284
26014
|
var _this11 = this;
|
|
25285
|
-
const _path = _this11._getFinalPath(
|
|
26015
|
+
const _path = _this11._getFinalPath(path17);
|
|
25286
26016
|
try {
|
|
25287
26017
|
await head(_this11.fetch, `${_this11.url}/object/${_path}`, { headers: _this11.headers });
|
|
25288
26018
|
return {
|
|
@@ -25360,8 +26090,8 @@ var StorageFileApi = class extends BaseApiClient {
|
|
|
25360
26090
|
* - `objects` table permissions: none
|
|
25361
26091
|
* - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
|
|
25362
26092
|
*/
|
|
25363
|
-
getPublicUrl(
|
|
25364
|
-
const _path = this._getFinalPath(
|
|
26093
|
+
getPublicUrl(path17, options) {
|
|
26094
|
+
const _path = this._getFinalPath(path17);
|
|
25365
26095
|
const query = new URLSearchParams();
|
|
25366
26096
|
if (options === null || options === void 0 ? void 0 : options.download) query.set("download", options.download === true ? "" : options.download);
|
|
25367
26097
|
if (options === null || options === void 0 ? void 0 : options.transform) this.applyTransformOptsToQuery(query, options.transform);
|
|
@@ -25498,10 +26228,10 @@ var StorageFileApi = class extends BaseApiClient {
|
|
|
25498
26228
|
* - `objects` table permissions: `select`
|
|
25499
26229
|
* - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
|
|
25500
26230
|
*/
|
|
25501
|
-
async list(
|
|
26231
|
+
async list(path17, options, parameters) {
|
|
25502
26232
|
var _this13 = this;
|
|
25503
26233
|
return _this13.handleOperation(async () => {
|
|
25504
|
-
const body = _objectSpread22(_objectSpread22(_objectSpread22({}, DEFAULT_SEARCH_OPTIONS), options), {}, { prefix:
|
|
26234
|
+
const body = _objectSpread22(_objectSpread22(_objectSpread22({}, DEFAULT_SEARCH_OPTIONS), options), {}, { prefix: path17 || "" });
|
|
25505
26235
|
return await post(_this13.fetch, `${_this13.url}/object/list/${_this13.bucketId}`, body, { headers: _this13.headers }, parameters);
|
|
25506
26236
|
});
|
|
25507
26237
|
}
|
|
@@ -25565,11 +26295,11 @@ var StorageFileApi = class extends BaseApiClient {
|
|
|
25565
26295
|
if (typeof Buffer !== "undefined") return Buffer.from(data).toString("base64");
|
|
25566
26296
|
return btoa(data);
|
|
25567
26297
|
}
|
|
25568
|
-
_getFinalPath(
|
|
25569
|
-
return `${this.bucketId}/${
|
|
26298
|
+
_getFinalPath(path17) {
|
|
26299
|
+
return `${this.bucketId}/${path17.replace(/^\/+/, "")}`;
|
|
25570
26300
|
}
|
|
25571
|
-
_removeEmptyFolders(
|
|
25572
|
-
return
|
|
26301
|
+
_removeEmptyFolders(path17) {
|
|
26302
|
+
return path17.replace(/^\/|\/$/g, "").replace(/\/+/g, "/");
|
|
25573
26303
|
}
|
|
25574
26304
|
/** Modifies the `query`, appending values the from `transform` */
|
|
25575
26305
|
applyTransformOptsToQuery(query, transform) {
|
|
@@ -27312,7 +28042,7 @@ function decodeJWT(token) {
|
|
|
27312
28042
|
};
|
|
27313
28043
|
return data;
|
|
27314
28044
|
}
|
|
27315
|
-
async function
|
|
28045
|
+
async function sleep4(time) {
|
|
27316
28046
|
return await new Promise((accept) => {
|
|
27317
28047
|
setTimeout(() => accept(null), time);
|
|
27318
28048
|
});
|
|
@@ -33105,7 +33835,7 @@ var GoTrueClient = class _GoTrueClient {
|
|
|
33105
33835
|
const startedAt = Date.now();
|
|
33106
33836
|
return await retryable(async (attempt) => {
|
|
33107
33837
|
if (attempt > 0) {
|
|
33108
|
-
await
|
|
33838
|
+
await sleep4(200 * Math.pow(2, attempt - 1));
|
|
33109
33839
|
}
|
|
33110
33840
|
this._debug(debugName, "refreshing attempt", attempt);
|
|
33111
33841
|
return await _request(this.fetch, "POST", `${this.url}/token?grant_type=refresh_token`, {
|
|
@@ -34644,7 +35374,7 @@ function shouldShowDeprecationWarning() {
|
|
|
34644
35374
|
}
|
|
34645
35375
|
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
35376
|
|
|
34647
|
-
// node_modules/@remixhq/core/dist/chunk-
|
|
35377
|
+
// node_modules/@remixhq/core/dist/chunk-XETDXVGM.js
|
|
34648
35378
|
var storedSessionSchema = external_exports.object({
|
|
34649
35379
|
access_token: external_exports.string().min(1),
|
|
34650
35380
|
refresh_token: external_exports.string().min(1),
|
|
@@ -34677,24 +35407,24 @@ async function maybeLoadKeytar() {
|
|
|
34677
35407
|
}
|
|
34678
35408
|
async function ensurePathPermissions(filePath) {
|
|
34679
35409
|
const dir = import_path7.default.dirname(filePath);
|
|
34680
|
-
await
|
|
35410
|
+
await import_promises22.default.mkdir(dir, { recursive: true });
|
|
34681
35411
|
try {
|
|
34682
|
-
await
|
|
35412
|
+
await import_promises22.default.chmod(dir, 448);
|
|
34683
35413
|
} catch {
|
|
34684
35414
|
}
|
|
34685
35415
|
try {
|
|
34686
|
-
await
|
|
35416
|
+
await import_promises22.default.chmod(filePath, 384);
|
|
34687
35417
|
} catch {
|
|
34688
35418
|
}
|
|
34689
35419
|
}
|
|
34690
|
-
async function
|
|
34691
|
-
await
|
|
35420
|
+
async function writeJsonAtomic3(filePath, value) {
|
|
35421
|
+
await import_promises22.default.mkdir(import_path7.default.dirname(filePath), { recursive: true });
|
|
34692
35422
|
const tmpPath = `${filePath}.tmp-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
34693
|
-
await
|
|
34694
|
-
await
|
|
35423
|
+
await import_promises22.default.writeFile(tmpPath, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
35424
|
+
await import_promises22.default.rename(tmpPath, filePath);
|
|
34695
35425
|
}
|
|
34696
35426
|
async function writeSessionFileFallback(filePath, session) {
|
|
34697
|
-
await
|
|
35427
|
+
await writeJsonAtomic3(filePath, session);
|
|
34698
35428
|
await ensurePathPermissions(filePath);
|
|
34699
35429
|
}
|
|
34700
35430
|
function createLocalSessionStore(params) {
|
|
@@ -34714,7 +35444,7 @@ function createLocalSessionStore(params) {
|
|
|
34714
35444
|
}
|
|
34715
35445
|
}
|
|
34716
35446
|
async function readFile() {
|
|
34717
|
-
const raw = await
|
|
35447
|
+
const raw = await import_promises22.default.readFile(filePath, "utf8").catch(() => null);
|
|
34718
35448
|
if (!raw) return null;
|
|
34719
35449
|
try {
|
|
34720
35450
|
const parsed = storedSessionSchema.safeParse(JSON.parse(raw));
|
|
@@ -34858,7 +35588,7 @@ function createSupabaseAuthHelpers(config) {
|
|
|
34858
35588
|
};
|
|
34859
35589
|
}
|
|
34860
35590
|
|
|
34861
|
-
// node_modules/@remixhq/core/dist/chunk-
|
|
35591
|
+
// node_modules/@remixhq/core/dist/chunk-XCZRNB35.js
|
|
34862
35592
|
var DEFAULT_API_URL = "https://api.remix.one";
|
|
34863
35593
|
var DEFAULT_SUPABASE_URL = "https://xtfxwbckjpfmqubnsusu.supabase.co";
|
|
34864
35594
|
var DEFAULT_SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inh0Znh3YmNranBmbXF1Ym5zdXN1Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjA2MDEyMzAsImV4cCI6MjA3NjE3NzIzMH0.dzWGAWrK4CvrmHVHzf8w7JlUZohdap0ZPnLZnABMV8s";
|
|
@@ -34896,6 +35626,7 @@ async function resolveConfig(_opts) {
|
|
|
34896
35626
|
}
|
|
34897
35627
|
|
|
34898
35628
|
// src/hook-auth.ts
|
|
35629
|
+
var HOOK_API_REQUEST_TIMEOUT_MS = 6e4;
|
|
34899
35630
|
async function createHookCollabApiClient() {
|
|
34900
35631
|
const config = await resolveConfig();
|
|
34901
35632
|
const sessionStore = createLocalSessionStore();
|
|
@@ -34908,307 +35639,11 @@ async function createHookCollabApiClient() {
|
|
|
34908
35639
|
}
|
|
34909
35640
|
});
|
|
34910
35641
|
return createApiClient(config, {
|
|
34911
|
-
tokenProvider
|
|
35642
|
+
tokenProvider,
|
|
35643
|
+
defaultRequestTimeoutMs: HOOK_API_REQUEST_TIMEOUT_MS
|
|
34912
35644
|
});
|
|
34913
35645
|
}
|
|
34914
35646
|
|
|
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
35647
|
// src/deferred-turn-drainer.ts
|
|
35213
35648
|
var collabFinalizeTurn2 = collabFinalizeTurn;
|
|
35214
35649
|
var drainPendingFinalizeQueue2 = drainPendingFinalizeQueue;
|
|
@@ -35218,6 +35653,16 @@ var HOOK_ACTOR = {
|
|
|
35218
35653
|
version: pluginMetadata.version,
|
|
35219
35654
|
provider: "anthropic"
|
|
35220
35655
|
};
|
|
35656
|
+
function getDrainerErrorDetails(error) {
|
|
35657
|
+
if (error instanceof Error) {
|
|
35658
|
+
const hint = typeof error.hint === "string" ? String(error.hint) : null;
|
|
35659
|
+
const codeRaw = error.code;
|
|
35660
|
+
const preflightCode = isFinalizePreflightFailureCode(codeRaw) ? codeRaw : null;
|
|
35661
|
+
return { message: error.message || "Deferred turn recording failed.", hint, preflightCode };
|
|
35662
|
+
}
|
|
35663
|
+
const message = typeof error === "string" && error.trim() ? error.trim() : "Deferred turn recording failed.";
|
|
35664
|
+
return { message, hint: null, preflightCode: null };
|
|
35665
|
+
}
|
|
35221
35666
|
var DEFERRED_TURN_DRAIN_POLL_INTERVAL_MS = 3e3;
|
|
35222
35667
|
var DEFERRED_TURN_DRAIN_MAX_WAIT_MS = 15 * 60 * 1e3;
|
|
35223
35668
|
var DEFERRED_TURN_DRAIN_LOCK_HEARTBEAT_MS = 3e4;
|
|
@@ -35236,10 +35681,10 @@ function repoLockFileName(repoRoot) {
|
|
|
35236
35681
|
return `.drainer-${hash}.lock`;
|
|
35237
35682
|
}
|
|
35238
35683
|
function repoLockPath(repoRoot) {
|
|
35239
|
-
return
|
|
35684
|
+
return import_node_path11.default.join(getDeferredTurnDirPath(), repoLockFileName(repoRoot));
|
|
35240
35685
|
}
|
|
35241
35686
|
async function readDrainLockMetadata(lockPath) {
|
|
35242
|
-
const raw = await
|
|
35687
|
+
const raw = await import_promises23.default.readFile(lockPath, "utf8").catch(() => null);
|
|
35243
35688
|
if (!raw) return null;
|
|
35244
35689
|
try {
|
|
35245
35690
|
const parsed = JSON.parse(raw);
|
|
@@ -35253,15 +35698,15 @@ async function readDrainLockMetadata(lockPath) {
|
|
|
35253
35698
|
}
|
|
35254
35699
|
async function writeDrainLockMetadata(lockPath, metadata) {
|
|
35255
35700
|
const tmpPath = `${lockPath}.tmp-${process.pid}-${Date.now()}`;
|
|
35256
|
-
await
|
|
35257
|
-
await
|
|
35701
|
+
await import_promises23.default.writeFile(tmpPath, JSON.stringify(metadata), "utf8");
|
|
35702
|
+
await import_promises23.default.rename(tmpPath, lockPath);
|
|
35258
35703
|
}
|
|
35259
35704
|
async function tryAcquireDrainLock(repoRoot) {
|
|
35260
35705
|
const lockPath = repoLockPath(repoRoot);
|
|
35261
|
-
await
|
|
35706
|
+
await import_promises23.default.mkdir(import_node_path11.default.dirname(lockPath), { recursive: true });
|
|
35262
35707
|
const existingMeta = await readDrainLockMetadata(lockPath);
|
|
35263
35708
|
if (existingMeta) {
|
|
35264
|
-
const lockStat = await
|
|
35709
|
+
const lockStat = await import_promises23.default.stat(lockPath).catch(() => null);
|
|
35265
35710
|
const ageMs = lockStat ? Date.now() - lockStat.mtimeMs : Number.POSITIVE_INFINITY;
|
|
35266
35711
|
const fresh = ageMs <= DEFERRED_TURN_DRAIN_LOCK_STALE_MS;
|
|
35267
35712
|
const alive = isPidAlive(existingMeta.pid);
|
|
@@ -35279,11 +35724,11 @@ async function tryAcquireDrainLock(repoRoot) {
|
|
|
35279
35724
|
async function releaseDrainLock(lockPath) {
|
|
35280
35725
|
const meta = await readDrainLockMetadata(lockPath);
|
|
35281
35726
|
if (meta && meta.pid !== process.pid) return;
|
|
35282
|
-
await
|
|
35727
|
+
await import_promises23.default.rm(lockPath, { force: true }).catch(() => void 0);
|
|
35283
35728
|
}
|
|
35284
35729
|
async function heartbeatDrainLock(lockPath) {
|
|
35285
35730
|
const now = /* @__PURE__ */ new Date();
|
|
35286
|
-
await
|
|
35731
|
+
await import_promises23.default.utimes(lockPath, now, now).catch(() => void 0);
|
|
35287
35732
|
}
|
|
35288
35733
|
async function sleep5(ms) {
|
|
35289
35734
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -35383,6 +35828,7 @@ async function runStandaloneDeferredTurnDrainer(repoRoot) {
|
|
|
35383
35828
|
let api = null;
|
|
35384
35829
|
let recordedTotal = 0;
|
|
35385
35830
|
let failedTotal = 0;
|
|
35831
|
+
let droppedTotal = 0;
|
|
35386
35832
|
let exitReason = "queue_empty";
|
|
35387
35833
|
try {
|
|
35388
35834
|
while (true) {
|
|
@@ -35413,7 +35859,49 @@ async function runStandaloneDeferredTurnDrainer(repoRoot) {
|
|
|
35413
35859
|
const bindingState = await readCollabBindingState(repoRoot).catch(() => null);
|
|
35414
35860
|
const currentBranch = bindingState?.currentBranch ?? null;
|
|
35415
35861
|
const isCurrentBranchBound = bindingState?.binding != null;
|
|
35416
|
-
const
|
|
35862
|
+
const currentAppId = bindingState?.binding?.currentAppId ?? null;
|
|
35863
|
+
const currentProjectId = bindingState?.binding?.projectId ?? bindingState?.projectId ?? null;
|
|
35864
|
+
let droppedThisPass = 0;
|
|
35865
|
+
const liveEntries = [];
|
|
35866
|
+
for (const entry of entries) {
|
|
35867
|
+
const appIdMismatch = entry.record.appIdAtDefer != null && currentAppId != null && entry.record.appIdAtDefer !== currentAppId;
|
|
35868
|
+
const projectIdMismatch = entry.record.projectIdAtDefer != null && currentProjectId != null && entry.record.projectIdAtDefer !== currentProjectId;
|
|
35869
|
+
if (appIdMismatch || projectIdMismatch) {
|
|
35870
|
+
await deleteDeferredTurnFile(entry.filePath);
|
|
35871
|
+
droppedThisPass += 1;
|
|
35872
|
+
await appendHookDiagnosticsEvent({
|
|
35873
|
+
hook: "deferredTurnDrainer",
|
|
35874
|
+
sessionId: sessionMarker,
|
|
35875
|
+
stage: "deferred_turn_dropped",
|
|
35876
|
+
result: "info",
|
|
35877
|
+
reason: appIdMismatch ? "app_id_mismatch" : "project_id_mismatch",
|
|
35878
|
+
repoRoot,
|
|
35879
|
+
fields: {
|
|
35880
|
+
deferredTurnId: entry.record.turnId,
|
|
35881
|
+
deferredSessionId: entry.record.sessionId,
|
|
35882
|
+
appIdAtDefer: entry.record.appIdAtDefer,
|
|
35883
|
+
projectIdAtDefer: entry.record.projectIdAtDefer,
|
|
35884
|
+
currentAppId,
|
|
35885
|
+
currentProjectId
|
|
35886
|
+
}
|
|
35887
|
+
});
|
|
35888
|
+
continue;
|
|
35889
|
+
}
|
|
35890
|
+
liveEntries.push(entry);
|
|
35891
|
+
}
|
|
35892
|
+
if (droppedThisPass > 0) {
|
|
35893
|
+
droppedTotal += droppedThisPass;
|
|
35894
|
+
}
|
|
35895
|
+
if (liveEntries.length === 0) {
|
|
35896
|
+
const remaining = await listDeferredTurnsForRepo(repoRoot).catch(() => []);
|
|
35897
|
+
if (remaining.length === 0) {
|
|
35898
|
+
exitReason = "queue_empty";
|
|
35899
|
+
break;
|
|
35900
|
+
}
|
|
35901
|
+
await sleep5(DEFERRED_TURN_DRAIN_POLL_INTERVAL_MS);
|
|
35902
|
+
continue;
|
|
35903
|
+
}
|
|
35904
|
+
const attemptable = liveEntries.filter(
|
|
35417
35905
|
(e) => isCurrentBranchBound && (!e.record.branchAtDefer || e.record.branchAtDefer === currentBranch)
|
|
35418
35906
|
);
|
|
35419
35907
|
if (attemptable.length === 0) {
|
|
@@ -35462,6 +35950,8 @@ async function runStandaloneDeferredTurnDrainer(repoRoot) {
|
|
|
35462
35950
|
} else {
|
|
35463
35951
|
failedThisPass += 1;
|
|
35464
35952
|
failedTotal += 1;
|
|
35953
|
+
const outcome = await recordDeferredTurnFailedAttempt(entry.filePath).catch(() => null);
|
|
35954
|
+
const promoted = outcome?.promoted === true;
|
|
35465
35955
|
await appendHookDiagnosticsEvent({
|
|
35466
35956
|
hook: "deferredTurnDrainer",
|
|
35467
35957
|
sessionId: sessionMarker,
|
|
@@ -35472,9 +35962,43 @@ async function runStandaloneDeferredTurnDrainer(repoRoot) {
|
|
|
35472
35962
|
message: result.error instanceof Error ? result.error.message : String(result.error ?? ""),
|
|
35473
35963
|
fields: {
|
|
35474
35964
|
deferredTurnId: entry.record.turnId,
|
|
35475
|
-
deferredSessionId: entry.record.sessionId
|
|
35965
|
+
deferredSessionId: entry.record.sessionId,
|
|
35966
|
+
attemptCount: outcome?.promoted === false ? outcome.newAttemptCount : outcome?.promoted === true ? outcome.finalAttemptCount : null,
|
|
35967
|
+
promoted
|
|
35476
35968
|
}
|
|
35477
35969
|
});
|
|
35970
|
+
if (promoted) {
|
|
35971
|
+
const errorDetails = getDrainerErrorDetails(result.error);
|
|
35972
|
+
await dispatchFinalizeFailure({
|
|
35973
|
+
// The dispatcher only knows about the two real Claude hook
|
|
35974
|
+
// entrypoints. The standalone drainer is logically a
|
|
35975
|
+
// post-Stop background process and the marker we're about
|
|
35976
|
+
// to write is consumed by the next prompt's UserPromptSubmit
|
|
35977
|
+
// hook, so attributing the failure to "Stop" matches what
|
|
35978
|
+
// the user will see.
|
|
35979
|
+
hook: "Stop",
|
|
35980
|
+
sessionId: sessionMarker,
|
|
35981
|
+
turnId: entry.record.turnId,
|
|
35982
|
+
repoRoot,
|
|
35983
|
+
preflightCode: errorDetails.preflightCode,
|
|
35984
|
+
message: `Deferred turn could not be recorded after ${outcome?.finalAttemptCount ?? "max"} attempts: ${errorDetails.message}`,
|
|
35985
|
+
hint: errorDetails.hint
|
|
35986
|
+
}).catch(async (dispatchErr) => {
|
|
35987
|
+
await appendHookDiagnosticsEvent({
|
|
35988
|
+
hook: "deferredTurnDrainer",
|
|
35989
|
+
sessionId: sessionMarker,
|
|
35990
|
+
stage: "deferred_turn_promotion_dispatch_failed",
|
|
35991
|
+
result: "error",
|
|
35992
|
+
reason: "exception",
|
|
35993
|
+
repoRoot,
|
|
35994
|
+
message: dispatchErr instanceof Error ? dispatchErr.message : String(dispatchErr),
|
|
35995
|
+
fields: {
|
|
35996
|
+
deferredTurnId: entry.record.turnId,
|
|
35997
|
+
deferredSessionId: entry.record.sessionId
|
|
35998
|
+
}
|
|
35999
|
+
});
|
|
36000
|
+
});
|
|
36001
|
+
}
|
|
35478
36002
|
}
|
|
35479
36003
|
}
|
|
35480
36004
|
if (recordedThisPass > 0) {
|
|
@@ -35527,6 +36051,7 @@ async function runStandaloneDeferredTurnDrainer(repoRoot) {
|
|
|
35527
36051
|
fields: {
|
|
35528
36052
|
recordedTotal,
|
|
35529
36053
|
failedTotal,
|
|
36054
|
+
droppedTotal,
|
|
35530
36055
|
elapsedMs: Date.now() - startedAt
|
|
35531
36056
|
}
|
|
35532
36057
|
});
|
|
@@ -35555,34 +36080,13 @@ async function maybeRunDeferredTurnDrainerFromArgv() {
|
|
|
35555
36080
|
return true;
|
|
35556
36081
|
}
|
|
35557
36082
|
|
|
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
36083
|
// src/spawn-helpers.ts
|
|
35580
|
-
var
|
|
36084
|
+
var import_node_child_process7 = require("child_process");
|
|
35581
36085
|
function spawnDeferredTurnDrainer(repoRoot) {
|
|
35582
36086
|
const entrypoint = process.argv[1];
|
|
35583
36087
|
if (!entrypoint) return;
|
|
35584
36088
|
if (!repoRoot) return;
|
|
35585
|
-
const child = (0,
|
|
36089
|
+
const child = (0, import_node_child_process7.spawn)(
|
|
35586
36090
|
process.execPath,
|
|
35587
36091
|
[...process.execArgv, entrypoint, "--drain-deferred-turns", repoRoot],
|
|
35588
36092
|
{
|
|
@@ -35596,7 +36100,7 @@ function spawnDeferredTurnDrainer(repoRoot) {
|
|
|
35596
36100
|
|
|
35597
36101
|
// src/hook-utils.ts
|
|
35598
36102
|
var import_promises24 = __toESM(require("fs/promises"), 1);
|
|
35599
|
-
var
|
|
36103
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
35600
36104
|
async function readJsonStdin() {
|
|
35601
36105
|
const chunks = [];
|
|
35602
36106
|
for await (const chunk of process.stdin) {
|
|
@@ -35622,16 +36126,16 @@ function extractString(input, keys) {
|
|
|
35622
36126
|
}
|
|
35623
36127
|
async function findBoundRepo(startPath) {
|
|
35624
36128
|
if (!startPath) return null;
|
|
35625
|
-
let current =
|
|
36129
|
+
let current = import_node_path12.default.resolve(startPath);
|
|
35626
36130
|
let stats = await import_promises24.default.stat(current).catch(() => null);
|
|
35627
36131
|
if (stats?.isFile()) {
|
|
35628
|
-
current =
|
|
36132
|
+
current = import_node_path12.default.dirname(current);
|
|
35629
36133
|
}
|
|
35630
36134
|
while (true) {
|
|
35631
|
-
const bindingPath =
|
|
36135
|
+
const bindingPath = import_node_path12.default.join(current, ".remix", "config.json");
|
|
35632
36136
|
const bindingStats = await import_promises24.default.stat(bindingPath).catch(() => null);
|
|
35633
36137
|
if (bindingStats?.isFile()) return current;
|
|
35634
|
-
const parent =
|
|
36138
|
+
const parent = import_node_path12.default.dirname(current);
|
|
35635
36139
|
if (parent === current) return null;
|
|
35636
36140
|
current = parent;
|
|
35637
36141
|
}
|
|
@@ -35655,8 +36159,8 @@ function buildRuntimeStatusOverride() {
|
|
|
35655
36159
|
"Use `remix_collab_drain_finalize_queue` only for explicit recovery flows, such as status reporting `await_finalize` before a merge-related operation."
|
|
35656
36160
|
].join("\n");
|
|
35657
36161
|
}
|
|
35658
|
-
var COLLAB_INIT_LOG_REL =
|
|
35659
|
-
var COLLAB_INIT_SPAWN_LOCK_REL =
|
|
36162
|
+
var COLLAB_INIT_LOG_REL = import_node_path13.default.join(".remix", "collab-init.log");
|
|
36163
|
+
var COLLAB_INIT_SPAWN_LOCK_REL = import_node_path13.default.join(".remix", ".collab-init-spawning");
|
|
35660
36164
|
var COLLAB_INIT_SPAWN_LOCK_STALE_MS = 90 * 1e3;
|
|
35661
36165
|
function isPidAlive2(pid) {
|
|
35662
36166
|
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
@@ -35667,31 +36171,44 @@ function isPidAlive2(pid) {
|
|
|
35667
36171
|
return false;
|
|
35668
36172
|
}
|
|
35669
36173
|
}
|
|
35670
|
-
function
|
|
36174
|
+
function readSpawnLock(spawnLockPath) {
|
|
35671
36175
|
try {
|
|
35672
|
-
const raw = (0,
|
|
36176
|
+
const raw = (0, import_node_fs7.readFileSync)(spawnLockPath, "utf8").trim();
|
|
35673
36177
|
if (!raw) return null;
|
|
36178
|
+
if (raw.startsWith("{")) {
|
|
36179
|
+
const parsed = JSON.parse(raw);
|
|
36180
|
+
const pid2 = Number(parsed?.pid ?? 0);
|
|
36181
|
+
return {
|
|
36182
|
+
pid: Number.isFinite(pid2) && pid2 > 0 ? pid2 : null,
|
|
36183
|
+
branchName: typeof parsed?.branchName === "string" && parsed.branchName.trim() ? parsed.branchName : null
|
|
36184
|
+
};
|
|
36185
|
+
}
|
|
35674
36186
|
const pid = Number.parseInt(raw, 10);
|
|
35675
|
-
return
|
|
36187
|
+
return {
|
|
36188
|
+
pid: Number.isFinite(pid) && pid > 0 ? pid : null,
|
|
36189
|
+
branchName: null
|
|
36190
|
+
};
|
|
35676
36191
|
} catch {
|
|
35677
36192
|
return null;
|
|
35678
36193
|
}
|
|
35679
36194
|
}
|
|
35680
|
-
function maybeAutoSpawnBranchInit(repoRoot) {
|
|
35681
|
-
const remixDir =
|
|
35682
|
-
const spawnLockPath =
|
|
35683
|
-
const logPath =
|
|
36195
|
+
function maybeAutoSpawnBranchInit(repoRoot, branchName) {
|
|
36196
|
+
const remixDir = import_node_path13.default.join(repoRoot, ".remix");
|
|
36197
|
+
const spawnLockPath = import_node_path13.default.join(repoRoot, COLLAB_INIT_SPAWN_LOCK_REL);
|
|
36198
|
+
const logPath = import_node_path13.default.join(repoRoot, COLLAB_INIT_LOG_REL);
|
|
35684
36199
|
try {
|
|
35685
|
-
if ((0,
|
|
35686
|
-
const
|
|
36200
|
+
if ((0, import_node_fs7.existsSync)(spawnLockPath)) {
|
|
36201
|
+
const lock = readSpawnLock(spawnLockPath);
|
|
36202
|
+
const lockPid = lock?.pid ?? null;
|
|
35687
36203
|
const lockAlive = lockPid !== null && isPidAlive2(lockPid);
|
|
35688
|
-
const
|
|
35689
|
-
|
|
36204
|
+
const sameBranch = !lock?.branchName || !branchName || lock.branchName === branchName;
|
|
36205
|
+
const ageMs = Date.now() - (0, import_node_fs7.statSync)(spawnLockPath).mtimeMs;
|
|
36206
|
+
if (lockAlive && sameBranch && ageMs < COLLAB_INIT_SPAWN_LOCK_STALE_MS) {
|
|
35690
36207
|
return { spawned: false, reason: "spawn_lock_held" };
|
|
35691
36208
|
}
|
|
35692
36209
|
if (!lockAlive) {
|
|
35693
36210
|
try {
|
|
35694
|
-
(0,
|
|
36211
|
+
(0, import_node_fs7.unlinkSync)(spawnLockPath);
|
|
35695
36212
|
} catch {
|
|
35696
36213
|
}
|
|
35697
36214
|
}
|
|
@@ -35699,14 +36216,14 @@ function maybeAutoSpawnBranchInit(repoRoot) {
|
|
|
35699
36216
|
} catch {
|
|
35700
36217
|
}
|
|
35701
36218
|
try {
|
|
35702
|
-
(0,
|
|
36219
|
+
(0, import_node_fs7.mkdirSync)(remixDir, { recursive: true });
|
|
35703
36220
|
} catch {
|
|
35704
36221
|
}
|
|
35705
36222
|
let out;
|
|
35706
36223
|
let err;
|
|
35707
36224
|
try {
|
|
35708
|
-
out = (0,
|
|
35709
|
-
err = (0,
|
|
36225
|
+
out = (0, import_node_fs7.openSync)(logPath, "a");
|
|
36226
|
+
err = (0, import_node_fs7.openSync)(logPath, "a");
|
|
35710
36227
|
} catch (logErr) {
|
|
35711
36228
|
return {
|
|
35712
36229
|
spawned: false,
|
|
@@ -35715,7 +36232,14 @@ function maybeAutoSpawnBranchInit(repoRoot) {
|
|
|
35715
36232
|
};
|
|
35716
36233
|
}
|
|
35717
36234
|
try {
|
|
35718
|
-
|
|
36235
|
+
(0, import_node_fs7.appendFileSync)(
|
|
36236
|
+
logPath,
|
|
36237
|
+
`
|
|
36238
|
+
[${(/* @__PURE__ */ new Date()).toISOString()}] auto-spawning remix collab init for branch=${branchName ?? "(unknown)"} repo=${repoRoot}
|
|
36239
|
+
`,
|
|
36240
|
+
"utf8"
|
|
36241
|
+
);
|
|
36242
|
+
const child = (0, import_node_child_process8.spawn)("remix", ["collab", "init"], {
|
|
35719
36243
|
cwd: repoRoot,
|
|
35720
36244
|
detached: true,
|
|
35721
36245
|
stdio: ["ignore", out, err],
|
|
@@ -35723,8 +36247,17 @@ function maybeAutoSpawnBranchInit(repoRoot) {
|
|
|
35723
36247
|
});
|
|
35724
36248
|
child.unref();
|
|
35725
36249
|
try {
|
|
35726
|
-
|
|
35727
|
-
|
|
36250
|
+
const lock = {
|
|
36251
|
+
schemaVersion: 1,
|
|
36252
|
+
pid: child.pid ?? null,
|
|
36253
|
+
branchName,
|
|
36254
|
+
repoRoot,
|
|
36255
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
36256
|
+
command: "remix collab init"
|
|
36257
|
+
};
|
|
36258
|
+
(0, import_node_fs7.writeFileSync)(spawnLockPath, `${JSON.stringify(lock, null, 2)}
|
|
36259
|
+
`, "utf8");
|
|
36260
|
+
(0, import_node_fs7.utimesSync)(spawnLockPath, /* @__PURE__ */ new Date(), /* @__PURE__ */ new Date());
|
|
35728
36261
|
} catch {
|
|
35729
36262
|
}
|
|
35730
36263
|
return { spawned: true, pid: child.pid, logPath };
|
|
@@ -35742,7 +36275,7 @@ function buildBranchInitContextMessage(branch, repoRoot, logPath) {
|
|
|
35742
36275
|
"[Remix recovery in progress]:",
|
|
35743
36276
|
`Remix is initializing recording for ${branchLabel} in ${repoRoot} in the background.`,
|
|
35744
36277
|
"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,
|
|
36278
|
+
"Do NOT call any Remix MCP tool to initialize, repair, or sync this branch \u2014 the plugin is handling it automatically.",
|
|
35746
36279
|
`Init log: ${logPath}`
|
|
35747
36280
|
].join("\n");
|
|
35748
36281
|
}
|
|
@@ -35942,7 +36475,7 @@ async function runHookUserPrompt(payload) {
|
|
|
35942
36475
|
}
|
|
35943
36476
|
if (isCurrentBranchUnbound) {
|
|
35944
36477
|
const currentBranch = bindingState?.currentBranch ?? null;
|
|
35945
|
-
const outcome = maybeAutoSpawnBranchInit(boundRepo);
|
|
36478
|
+
const outcome = maybeAutoSpawnBranchInit(boundRepo, currentBranch);
|
|
35946
36479
|
if (outcome.spawned) {
|
|
35947
36480
|
advisorySections.push(buildBranchInitContextMessage(currentBranch, boundRepo, outcome.logPath));
|
|
35948
36481
|
await appendHookDiagnosticsEvent({
|