@remixhq/claude-plugin 0.1.2 → 0.1.3

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.
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/hook-stop-collab.ts
4
- import { collabAdd, collabRecordTurn } from "@remixhq/core/collab";
5
- import { readCollabBinding } from "@remixhq/core/binding";
6
- import { findGitRoot, getWorkspaceDiff } from "@remixhq/core/repo";
4
+ import { collabAdd, collabRecordTurn, collabRecordingPreflight } from "@remixhq/core/collab";
5
+ import { readCollabBinding as readCollabBinding2 } from "@remixhq/core/binding";
6
+ import { getWorkspaceDiff } from "@remixhq/core/repo";
7
7
 
8
8
  // src/hook-auth.ts
9
9
  import {
@@ -40,6 +40,175 @@ function stateRoot() {
40
40
  function statePath(sessionId) {
41
41
  return path.join(stateRoot(), `${sessionId}.json`);
42
42
  }
43
+ function stateLockPath(sessionId) {
44
+ return path.join(stateRoot(), `${sessionId}.lock`);
45
+ }
46
+ function stateLockMetaPath(sessionId) {
47
+ return path.join(stateLockPath(sessionId), "owner.json");
48
+ }
49
+ async function writeJsonAtomic(filePath, value) {
50
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
51
+ const tmpPath = `${filePath}.tmp-${Date.now()}-${Math.random().toString(16).slice(2)}`;
52
+ await fs.writeFile(tmpPath, JSON.stringify(value, null, 2) + "\n", "utf8");
53
+ await fs.rename(tmpPath, filePath);
54
+ }
55
+ var STATE_LOCK_WAIT_MS = 2e3;
56
+ var STATE_LOCK_POLL_MS = 25;
57
+ var STATE_LOCK_STALE_MS = 3e4;
58
+ var STATE_LOCK_HEARTBEAT_MS = 5e3;
59
+ async function sleep(ms) {
60
+ await new Promise((resolve) => setTimeout(resolve, ms));
61
+ }
62
+ async function readStateLockMetadata(sessionId) {
63
+ const raw = await fs.readFile(stateLockMetaPath(sessionId), "utf8").catch(() => null);
64
+ if (!raw) return null;
65
+ try {
66
+ const parsed = JSON.parse(raw);
67
+ if (typeof parsed.ownerId !== "string" || typeof parsed.pid !== "number" || typeof parsed.createdAt !== "string" || typeof parsed.heartbeatAt !== "string") {
68
+ return null;
69
+ }
70
+ return {
71
+ ownerId: parsed.ownerId,
72
+ pid: parsed.pid,
73
+ createdAt: parsed.createdAt,
74
+ heartbeatAt: parsed.heartbeatAt
75
+ };
76
+ } catch {
77
+ return null;
78
+ }
79
+ }
80
+ async function writeStateLockMetadata(sessionId, metadata) {
81
+ await writeJsonAtomic(stateLockMetaPath(sessionId), metadata);
82
+ }
83
+ async function tryRemoveStaleStateLock(sessionId) {
84
+ const lockPath = stateLockPath(sessionId);
85
+ const metadata = await readStateLockMetadata(sessionId);
86
+ const staleByHeartbeat = metadata && Date.now() - new Date(metadata.heartbeatAt).getTime() > STATE_LOCK_STALE_MS;
87
+ if (staleByHeartbeat) {
88
+ await fs.rm(lockPath, { recursive: true, force: true }).catch(() => void 0);
89
+ return true;
90
+ }
91
+ if (!metadata) {
92
+ const lockStat = await fs.stat(lockPath).catch(() => null);
93
+ if (lockStat && Date.now() - lockStat.mtimeMs > STATE_LOCK_STALE_MS) {
94
+ await fs.rm(lockPath, { recursive: true, force: true }).catch(() => void 0);
95
+ return true;
96
+ }
97
+ }
98
+ return false;
99
+ }
100
+ async function acquireStateLock(sessionId) {
101
+ const lockPath = stateLockPath(sessionId);
102
+ const deadline = Date.now() + STATE_LOCK_WAIT_MS;
103
+ while (true) {
104
+ try {
105
+ await fs.mkdir(lockPath);
106
+ const ownerId = randomUUID();
107
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
108
+ const metadata = {
109
+ ownerId,
110
+ pid: process.pid,
111
+ createdAt,
112
+ heartbeatAt: createdAt
113
+ };
114
+ await writeStateLockMetadata(sessionId, metadata);
115
+ let released = false;
116
+ const heartbeat = setInterval(() => {
117
+ if (released) return;
118
+ void writeStateLockMetadata(sessionId, {
119
+ ...metadata,
120
+ heartbeatAt: (/* @__PURE__ */ new Date()).toISOString()
121
+ }).catch(() => void 0);
122
+ }, STATE_LOCK_HEARTBEAT_MS);
123
+ heartbeat.unref?.();
124
+ return async () => {
125
+ if (released) return;
126
+ released = true;
127
+ clearInterval(heartbeat);
128
+ const currentMetadata = await readStateLockMetadata(sessionId);
129
+ if (currentMetadata?.ownerId === ownerId) {
130
+ await fs.rm(lockPath, { recursive: true, force: true }).catch(() => void 0);
131
+ }
132
+ };
133
+ } catch (error) {
134
+ const code = error && typeof error === "object" && "code" in error ? error.code : null;
135
+ if (code !== "EEXIST") {
136
+ throw error;
137
+ }
138
+ if (await tryRemoveStaleStateLock(sessionId)) {
139
+ continue;
140
+ }
141
+ if (Date.now() >= deadline) {
142
+ throw new Error(`Timed out acquiring hook state lock for session ${sessionId}.`);
143
+ }
144
+ await sleep(STATE_LOCK_POLL_MS);
145
+ }
146
+ }
147
+ }
148
+ async function withStateLock(sessionId, fn) {
149
+ const release = await acquireStateLock(sessionId);
150
+ try {
151
+ return await fn();
152
+ } finally {
153
+ await release();
154
+ }
155
+ }
156
+ function normalizeIntent(value) {
157
+ return value === "memory_first" || value === "collab_state" || value === "git_facts" ? value : "neutral";
158
+ }
159
+ function normalizeString(value) {
160
+ return typeof value === "string" && value.trim() ? value.trim() : null;
161
+ }
162
+ function normalizeStringArray(value) {
163
+ if (!Array.isArray(value)) return [];
164
+ return Array.from(
165
+ new Set(
166
+ value.filter((entry) => typeof entry === "string" && entry.trim().length > 0).map((entry) => entry.trim())
167
+ )
168
+ );
169
+ }
170
+ function normalizeTouchedRepo(value, repoRoot) {
171
+ if (!value || typeof value !== "object") return null;
172
+ const parsed = value;
173
+ const normalizedRepoRoot = normalizeString(parsed.repoRoot) ?? repoRoot.trim();
174
+ if (!normalizedRepoRoot) return null;
175
+ return {
176
+ repoRoot: normalizedRepoRoot,
177
+ projectId: normalizeString(parsed.projectId),
178
+ currentAppId: normalizeString(parsed.currentAppId),
179
+ upstreamAppId: normalizeString(parsed.upstreamAppId),
180
+ firstTouchedAt: normalizeString(parsed.firstTouchedAt) ?? (/* @__PURE__ */ new Date()).toISOString(),
181
+ lastTouchedAt: normalizeString(parsed.lastTouchedAt) ?? (/* @__PURE__ */ new Date()).toISOString(),
182
+ lastObservedWriteAt: normalizeString(parsed.lastObservedWriteAt),
183
+ touchedBy: normalizeStringArray(parsed.touchedBy),
184
+ hasObservedWrite: Boolean(parsed.hasObservedWrite),
185
+ manuallyRecorded: Boolean(parsed.manuallyRecorded),
186
+ manuallyRecordedAt: normalizeString(parsed.manuallyRecordedAt),
187
+ manuallyRecordedByTool: normalizeString(parsed.manuallyRecordedByTool),
188
+ stopAttempted: Boolean(parsed.stopAttempted),
189
+ stopRecorded: Boolean(parsed.stopRecorded),
190
+ stopRecordedAt: normalizeString(parsed.stopRecordedAt),
191
+ stopRecordedMode: parsed.stopRecordedMode === "changed_turn" || parsed.stopRecordedMode === "no_diff_turn" ? parsed.stopRecordedMode : null,
192
+ recordingFailureMessage: normalizeString(parsed.recordingFailureMessage),
193
+ recordingFailureHint: normalizeString(parsed.recordingFailureHint),
194
+ recordingFailedAt: normalizeString(parsed.recordingFailedAt)
195
+ };
196
+ }
197
+ function normalizeTouchedRepos(value) {
198
+ if (!value || typeof value !== "object") return {};
199
+ const entries = Object.entries(value).map(([repoRoot, repo]) => normalizeTouchedRepo(repo, repoRoot)).filter((repo) => repo !== null).sort((a, b) => a.repoRoot.localeCompare(b.repoRoot));
200
+ return Object.fromEntries(entries.map((repo) => [repo.repoRoot, repo]));
201
+ }
202
+ async function updatePendingTurnState(sessionId, updater) {
203
+ return withStateLock(sessionId, async () => {
204
+ const existing = await loadPendingTurnState(sessionId);
205
+ if (!existing) return null;
206
+ const result = updater(existing);
207
+ if (result === false) return existing;
208
+ await savePendingTurnState(existing);
209
+ return existing;
210
+ });
211
+ }
43
212
  async function loadPendingTurnState(sessionId) {
44
213
  const raw = await fs.readFile(statePath(sessionId), "utf8").catch(() => null);
45
214
  if (!raw) return null;
@@ -49,23 +218,75 @@ async function loadPendingTurnState(sessionId) {
49
218
  if (typeof parsed.sessionId !== "string" || typeof parsed.turnId !== "string" || typeof parsed.prompt !== "string") {
50
219
  return null;
51
220
  }
52
- const intent = parsed.intent;
53
221
  return {
54
222
  sessionId: parsed.sessionId,
55
223
  turnId: parsed.turnId,
56
224
  prompt: parsed.prompt,
57
- cwd: typeof parsed.cwd === "string" && parsed.cwd.trim() ? parsed.cwd : null,
58
- intent: intent === "memory_first" || intent === "collab_state" || intent === "git_facts" ? intent : "neutral",
225
+ initialCwd: normalizeString(parsed.initialCwd),
226
+ intent: normalizeIntent(parsed.intent),
59
227
  submittedAt: typeof parsed.submittedAt === "string" ? parsed.submittedAt : (/* @__PURE__ */ new Date()).toISOString(),
60
- recordedByTool: Boolean(parsed.recordedByTool),
61
- consultedMemory: Boolean(parsed.consultedMemory)
228
+ consultedMemory: Boolean(parsed.consultedMemory),
229
+ touchedRepos: normalizeTouchedRepos(parsed.touchedRepos),
230
+ turnFailureMessage: normalizeString(parsed.turnFailureMessage),
231
+ turnFailureHint: normalizeString(parsed.turnFailureHint),
232
+ turnFailedAt: normalizeString(parsed.turnFailedAt)
62
233
  };
63
234
  } catch {
64
235
  return null;
65
236
  }
66
237
  }
238
+ async function savePendingTurnState(state) {
239
+ await writeJsonAtomic(statePath(state.sessionId), state);
240
+ }
241
+ async function markTouchedRepoStopAttempted(sessionId, repoRoot) {
242
+ await updatePendingTurnState(sessionId, (existing) => {
243
+ const current = existing.touchedRepos[repoRoot];
244
+ if (!current) return false;
245
+ current.stopAttempted = true;
246
+ current.lastTouchedAt = (/* @__PURE__ */ new Date()).toISOString();
247
+ });
248
+ }
249
+ async function markTouchedRepoStopRecorded(sessionId, repoRoot, params) {
250
+ await updatePendingTurnState(sessionId, (existing) => {
251
+ const current = existing.touchedRepos[repoRoot];
252
+ if (!current) return false;
253
+ current.stopAttempted = true;
254
+ current.stopRecorded = true;
255
+ current.stopRecordedAt = (/* @__PURE__ */ new Date()).toISOString();
256
+ current.stopRecordedMode = params.mode;
257
+ current.recordingFailureMessage = null;
258
+ current.recordingFailureHint = null;
259
+ current.recordingFailedAt = null;
260
+ current.lastTouchedAt = (/* @__PURE__ */ new Date()).toISOString();
261
+ });
262
+ }
263
+ async function markTouchedRepoRecordingFailure(sessionId, repoRoot, params) {
264
+ await updatePendingTurnState(sessionId, (existing) => {
265
+ const current = existing.touchedRepos[repoRoot];
266
+ if (!current) return false;
267
+ current.stopAttempted = true;
268
+ current.recordingFailureMessage = params.message.trim();
269
+ current.recordingFailureHint = params.hint?.trim() || null;
270
+ current.recordingFailedAt = (/* @__PURE__ */ new Date()).toISOString();
271
+ current.lastTouchedAt = (/* @__PURE__ */ new Date()).toISOString();
272
+ });
273
+ }
274
+ async function markPendingTurnFailure(sessionId, params) {
275
+ await updatePendingTurnState(sessionId, (existing) => {
276
+ existing.turnFailureMessage = params.message.trim();
277
+ existing.turnFailureHint = params.hint?.trim() || null;
278
+ existing.turnFailedAt = (/* @__PURE__ */ new Date()).toISOString();
279
+ });
280
+ }
281
+ async function listTouchedRepos(sessionId) {
282
+ const existing = await loadPendingTurnState(sessionId);
283
+ if (!existing) return [];
284
+ return Object.values(existing.touchedRepos).sort((a, b) => a.repoRoot.localeCompare(b.repoRoot));
285
+ }
67
286
  async function clearPendingTurnState(sessionId) {
68
- await fs.rm(statePath(sessionId), { force: true }).catch(() => void 0);
287
+ await withStateLock(sessionId, async () => {
288
+ await fs.rm(statePath(sessionId), { force: true }).catch(() => void 0);
289
+ });
69
290
  }
70
291
 
71
292
  // src/metadata.ts
@@ -83,6 +304,7 @@ var pluginMetadata = {
83
304
  // src/hook-utils.ts
84
305
  import fs2 from "fs/promises";
85
306
  import path2 from "path";
307
+ import { readCollabBinding } from "@remixhq/core/binding";
86
308
  async function readJsonStdin() {
87
309
  const chunks = [];
88
310
  for await (const chunk of process.stdin) {
@@ -123,6 +345,123 @@ var HOOK_ACTOR = {
123
345
  version: pluginMetadata.version,
124
346
  provider: "anthropic"
125
347
  };
348
+ function getErrorDetails(error) {
349
+ if (error instanceof Error) {
350
+ const hint = typeof error.hint === "string" ? String(error.hint) : null;
351
+ return {
352
+ message: error.message || "Automatic Remix turn recording failed.",
353
+ hint
354
+ };
355
+ }
356
+ const message = typeof error === "string" && error.trim() ? error.trim() : "Automatic Remix turn recording failed.";
357
+ return { message, hint: null };
358
+ }
359
+ function getRecordingBlockedMessage(status, repoRoot) {
360
+ switch (status.status) {
361
+ case "not_git_repo":
362
+ return {
363
+ message: "Automatic Remix turn recording failed because the repository is no longer inside a git repository.",
364
+ hint: status.hint || `Repo root: ${repoRoot}`
365
+ };
366
+ case "not_bound":
367
+ return {
368
+ message: "Automatic Remix turn recording failed because the repository is no longer bound to Remix.",
369
+ hint: status.hint || `Repo root: ${repoRoot}`
370
+ };
371
+ case "missing_head":
372
+ return {
373
+ message: "Automatic Remix turn recording failed because the repository HEAD could not be resolved.",
374
+ hint: status.hint || `Repo root: ${repoRoot}`
375
+ };
376
+ case "branch_mismatch":
377
+ return {
378
+ message: "Automatic Remix turn recording was blocked by the checkout's preferred-branch policy.",
379
+ hint: status.hint || `Repo root: ${repoRoot}`
380
+ };
381
+ case "metadata_conflict":
382
+ return {
383
+ message: "Automatic Remix turn recording was blocked because local repository metadata conflicts with the bound Remix app.",
384
+ hint: status.hint || `Repo root: ${repoRoot}`
385
+ };
386
+ case "reconcile_required":
387
+ return {
388
+ message: "Automatic Remix turn recording was blocked because the repository must be reconciled before recording can continue safely.",
389
+ hint: status.hint || `Repo root: ${repoRoot}`
390
+ };
391
+ default:
392
+ return null;
393
+ }
394
+ }
395
+ function buildRepoIdempotencyKey(turnId, repo, mode) {
396
+ const repoToken = repo.currentAppId?.trim() || repo.repoRoot;
397
+ return `${turnId}:${repoToken}:${mode}`;
398
+ }
399
+ function shouldSkipStopRecording(repo) {
400
+ if (repo.stopRecorded) {
401
+ return true;
402
+ }
403
+ if (!repo.manuallyRecorded) {
404
+ return false;
405
+ }
406
+ if (!repo.manuallyRecordedAt) {
407
+ return false;
408
+ }
409
+ if (!repo.lastObservedWriteAt) {
410
+ return true;
411
+ }
412
+ return new Date(repo.lastObservedWriteAt).getTime() <= new Date(repo.manuallyRecordedAt).getTime();
413
+ }
414
+ async function recordTouchedRepo(params) {
415
+ const { sessionId, turnId, repo, prompt, assistantResponse, api } = params;
416
+ await markTouchedRepoStopAttempted(sessionId, repo.repoRoot);
417
+ try {
418
+ const binding = await readCollabBinding2(repo.repoRoot).catch(() => null);
419
+ if (!binding) {
420
+ await markTouchedRepoRecordingFailure(sessionId, repo.repoRoot, {
421
+ message: "Automatic Remix turn recording failed because the repository is no longer bound to Remix.",
422
+ hint: `Repo root: ${repo.repoRoot}`
423
+ });
424
+ return false;
425
+ }
426
+ const recordingPreflight = await collabRecordingPreflight({
427
+ api,
428
+ cwd: repo.repoRoot
429
+ });
430
+ const blocked = getRecordingBlockedMessage(recordingPreflight, repo.repoRoot);
431
+ if (blocked) {
432
+ await markTouchedRepoRecordingFailure(sessionId, repo.repoRoot, blocked);
433
+ return false;
434
+ }
435
+ const workspaceDiff = await getWorkspaceDiff(repo.repoRoot);
436
+ if (workspaceDiff.diff.trim()) {
437
+ await collabAdd({
438
+ api,
439
+ cwd: repo.repoRoot,
440
+ prompt,
441
+ assistantResponse,
442
+ diffSource: "worktree",
443
+ idempotencyKey: buildRepoIdempotencyKey(turnId, repo, "changed_turn"),
444
+ actor: HOOK_ACTOR
445
+ });
446
+ await markTouchedRepoStopRecorded(sessionId, repo.repoRoot, { mode: "changed_turn" });
447
+ return true;
448
+ }
449
+ await collabRecordTurn({
450
+ api,
451
+ cwd: repo.repoRoot,
452
+ prompt,
453
+ assistantResponse,
454
+ idempotencyKey: buildRepoIdempotencyKey(turnId, repo, "no_diff_turn"),
455
+ actor: HOOK_ACTOR
456
+ });
457
+ await markTouchedRepoStopRecorded(sessionId, repo.repoRoot, { mode: "no_diff_turn" });
458
+ return true;
459
+ } catch (error) {
460
+ const details = getErrorDetails(error);
461
+ await markTouchedRepoRecordingFailure(sessionId, repo.repoRoot, details);
462
+ return false;
463
+ }
464
+ }
126
465
  async function main() {
127
466
  const payload = await readJsonStdin();
128
467
  if (extractBoolean(payload, ["stop_hook_active"])) {
@@ -137,47 +476,43 @@ async function main() {
137
476
  return;
138
477
  }
139
478
  try {
140
- if (state.recordedByTool) {
479
+ const touchedRepos = await listTouchedRepos(sessionId);
480
+ if (touchedRepos.length === 0) {
481
+ await clearPendingTurnState(sessionId);
141
482
  return;
142
483
  }
143
484
  const prompt = state.prompt.trim();
144
485
  const assistantResponse = (extractString(payload, ["last_assistant_message"]) || "").trim();
145
486
  if (!prompt || !assistantResponse) {
146
- return;
147
- }
148
- const cwd = extractString(payload, ["cwd"]) ?? state.cwd ?? process.cwd();
149
- const repoRoot = await findGitRoot(cwd).catch(() => null);
150
- if (!repoRoot) {
151
- return;
152
- }
153
- const binding = await readCollabBinding(repoRoot).catch(() => null);
154
- if (!binding) {
487
+ await markPendingTurnFailure(sessionId, {
488
+ message: "Automatic Remix turn recording failed because the prompt or assistant response was missing."
489
+ });
155
490
  return;
156
491
  }
157
492
  const api = await createHookCollabApiClient();
158
- const workspaceDiff = await getWorkspaceDiff(repoRoot);
159
- if (workspaceDiff.diff.trim()) {
160
- await collabAdd({
161
- api,
162
- cwd: repoRoot,
493
+ let hadFailure = false;
494
+ for (const repo of touchedRepos) {
495
+ if (shouldSkipStopRecording(repo)) {
496
+ continue;
497
+ }
498
+ const recorded = await recordTouchedRepo({
499
+ sessionId,
500
+ turnId: state.turnId,
501
+ repo,
163
502
  prompt,
164
503
  assistantResponse,
165
- diffSource: "worktree",
166
- idempotencyKey: state.turnId,
167
- actor: HOOK_ACTOR
504
+ api
168
505
  });
169
- return;
506
+ if (!recorded) {
507
+ hadFailure = true;
508
+ }
170
509
  }
171
- await collabRecordTurn({
172
- api,
173
- cwd: repoRoot,
174
- prompt,
175
- assistantResponse,
176
- idempotencyKey: state.turnId,
177
- actor: HOOK_ACTOR
178
- });
179
- } finally {
180
- await clearPendingTurnState(sessionId);
510
+ if (!hadFailure) {
511
+ await clearPendingTurnState(sessionId);
512
+ }
513
+ } catch (error) {
514
+ const details = getErrorDetails(error);
515
+ await markPendingTurnFailure(sessionId, details);
181
516
  }
182
517
  }
183
518
  main().catch((error) => {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/hook-stop-collab.ts","../src/hook-auth.ts","../src/hook-state.ts","../src/metadata.ts","../src/hook-utils.ts"],"sourcesContent":["import { collabAdd, collabRecordTurn } from \"@remixhq/core/collab\";\nimport { readCollabBinding } from \"@remixhq/core/binding\";\nimport { findGitRoot, getWorkspaceDiff } from \"@remixhq/core/repo\";\n\nimport { createHookCollabApiClient } from \"./hook-auth.js\";\nimport { clearPendingTurnState, loadPendingTurnState } from \"./hook-state.js\";\nimport { pluginMetadata } from \"./metadata.js\";\nimport { extractBoolean, extractString, readJsonStdin } from \"./hook-utils.js\";\n\nconst HOOK_ACTOR = {\n type: \"agent\",\n name: \"claude-code\",\n version: pluginMetadata.version,\n provider: \"anthropic\",\n} as const;\n\nasync function main(): Promise<void> {\n const payload = await readJsonStdin();\n if (extractBoolean(payload, [\"stop_hook_active\"])) {\n return;\n }\n\n const sessionId = extractString(payload, [\"session_id\"]);\n if (!sessionId) {\n return;\n }\n\n const state = await loadPendingTurnState(sessionId);\n if (!state) {\n return;\n }\n\n try {\n if (state.recordedByTool) {\n return;\n }\n\n const prompt = state.prompt.trim();\n const assistantResponse = (extractString(payload, [\"last_assistant_message\"]) || \"\").trim();\n if (!prompt || !assistantResponse) {\n return;\n }\n\n const cwd = extractString(payload, [\"cwd\"]) ?? state.cwd ?? process.cwd();\n const repoRoot = await findGitRoot(cwd).catch(() => null);\n if (!repoRoot) {\n return;\n }\n\n const binding = await readCollabBinding(repoRoot).catch(() => null);\n if (!binding) {\n return;\n }\n\n const api = await createHookCollabApiClient();\n const workspaceDiff = await getWorkspaceDiff(repoRoot);\n if (workspaceDiff.diff.trim()) {\n await collabAdd({\n api,\n cwd: repoRoot,\n prompt,\n assistantResponse,\n diffSource: \"worktree\",\n idempotencyKey: state.turnId,\n actor: HOOK_ACTOR,\n });\n return;\n }\n\n await collabRecordTurn({\n api,\n cwd: repoRoot,\n prompt,\n assistantResponse,\n idempotencyKey: state.turnId,\n actor: HOOK_ACTOR,\n });\n } finally {\n await clearPendingTurnState(sessionId);\n }\n}\n\nmain().catch((error) => {\n const message = error instanceof Error ? error.message : String(error);\n process.stderr.write(`${message}\\n`);\n process.exitCode = 0;\n});\n","import {\n createApiClient,\n createLocalSessionStore,\n createStoredSessionTokenProvider,\n createSupabaseAuthHelpers,\n resolveConfig,\n} from \"@remixhq/core\";\nimport type { CollabApiClient } from \"@remixhq/core/collab\";\n\nexport async function createHookCollabApiClient(): Promise<CollabApiClient> {\n const config = await resolveConfig();\n const sessionStore = createLocalSessionStore();\n const tokenProvider = createStoredSessionTokenProvider({\n config,\n sessionStore,\n refreshStoredSession: async ({ config: refreshConfig, session }) => {\n const supabase = createSupabaseAuthHelpers(refreshConfig);\n return supabase.refreshWithStoredSession({ session });\n },\n });\n\n return createApiClient(config, {\n tokenProvider,\n }) as unknown as CollabApiClient;\n}\n","import fs from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { randomUUID } from \"node:crypto\";\n\nimport type { TurnIntent } from \"./history-routing.js\";\n\nexport type PendingTurnState = {\n sessionId: string;\n turnId: string;\n prompt: string;\n cwd: string | null;\n intent: TurnIntent;\n submittedAt: string;\n recordedByTool: boolean;\n consultedMemory: boolean;\n};\n\nfunction stateRoot(): string {\n return path.join(os.tmpdir(), \"remix-claude-plugin-hooks\");\n}\n\nfunction statePath(sessionId: string): string {\n return path.join(stateRoot(), `${sessionId}.json`);\n}\n\nasync function writeJsonAtomic(filePath: string, value: unknown): Promise<void> {\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n const tmpPath = `${filePath}.tmp-${Date.now()}-${Math.random().toString(16).slice(2)}`;\n await fs.writeFile(tmpPath, JSON.stringify(value, null, 2) + \"\\n\", \"utf8\");\n await fs.rename(tmpPath, filePath);\n}\n\nexport async function loadPendingTurnState(sessionId: string): Promise<PendingTurnState | null> {\n const raw = await fs.readFile(statePath(sessionId), \"utf8\").catch(() => null);\n if (!raw) return null;\n try {\n const parsed = JSON.parse(raw) as Partial<PendingTurnState>;\n if (!parsed || typeof parsed !== \"object\") return null;\n if (typeof parsed.sessionId !== \"string\" || typeof parsed.turnId !== \"string\" || typeof parsed.prompt !== \"string\") {\n return null;\n }\n\n const intent = parsed.intent;\n return {\n sessionId: parsed.sessionId,\n turnId: parsed.turnId,\n prompt: parsed.prompt,\n cwd: typeof parsed.cwd === \"string\" && parsed.cwd.trim() ? parsed.cwd : null,\n intent: intent === \"memory_first\" || intent === \"collab_state\" || intent === \"git_facts\" ? intent : \"neutral\",\n submittedAt: typeof parsed.submittedAt === \"string\" ? parsed.submittedAt : new Date().toISOString(),\n recordedByTool: Boolean(parsed.recordedByTool),\n consultedMemory: Boolean(parsed.consultedMemory),\n };\n } catch {\n return null;\n }\n}\n\nexport async function savePendingTurnState(state: PendingTurnState): Promise<void> {\n await writeJsonAtomic(statePath(state.sessionId), state);\n}\n\nexport async function createPendingTurnState(params: {\n sessionId: string;\n prompt: string;\n cwd?: string | null;\n intent: TurnIntent;\n}): Promise<PendingTurnState> {\n const state: PendingTurnState = {\n sessionId: params.sessionId,\n turnId: randomUUID(),\n prompt: params.prompt,\n cwd: params.cwd?.trim() || null,\n intent: params.intent,\n submittedAt: new Date().toISOString(),\n recordedByTool: false,\n consultedMemory: false,\n };\n await savePendingTurnState(state);\n return state;\n}\n\nexport async function markPendingTurnRecordedByTool(sessionId: string): Promise<void> {\n const existing = await loadPendingTurnState(sessionId);\n if (!existing) return;\n existing.recordedByTool = true;\n await savePendingTurnState(existing);\n}\n\nexport async function markPendingTurnConsultedMemory(sessionId: string): Promise<void> {\n const existing = await loadPendingTurnState(sessionId);\n if (!existing || existing.consultedMemory) return;\n existing.consultedMemory = true;\n await savePendingTurnState(existing);\n}\n\nexport async function clearPendingTurnState(sessionId: string): Promise<void> {\n await fs.rm(statePath(sessionId), { force: true }).catch(() => undefined);\n}\n","import { createRequire } from \"node:module\";\n\nconst require = createRequire(import.meta.url);\nconst pkg = require(\"../package.json\") as { name: string; version: string; description: string };\n\nexport const pluginMetadata = {\n name: pkg.name,\n version: pkg.version,\n description: pkg.description,\n pluginId: \"remix\",\n agentName: \"remix-collab\",\n};\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\n\nexport async function readJsonStdin(): Promise<Record<string, unknown>> {\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) {\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));\n }\n const raw = Buffer.concat(chunks).toString(\"utf8\").trim();\n if (!raw) return {};\n try {\n const parsed = JSON.parse(raw);\n return parsed && typeof parsed === \"object\" ? (parsed as Record<string, unknown>) : {};\n } catch {\n return {};\n }\n}\n\nfunction getNestedRecord(value: unknown): Record<string, unknown> | null {\n return value && typeof value === \"object\" ? (value as Record<string, unknown>) : null;\n}\n\nexport function extractToolInput(payload: Record<string, unknown>): Record<string, unknown> {\n return getNestedRecord(payload.tool_input) ?? getNestedRecord(payload.toolInput) ?? payload;\n}\n\nexport function extractString(input: Record<string, unknown>, keys: string[]): string | null {\n for (const key of keys) {\n const value = input[key];\n if (typeof value === \"string\" && value.trim()) {\n return value.trim();\n }\n }\n return null;\n}\n\nexport function extractBoolean(input: Record<string, unknown>, keys: string[]): boolean | null {\n for (const key of keys) {\n const value = input[key];\n if (typeof value === \"boolean\") {\n return value;\n }\n }\n return null;\n}\n\nexport async function findBoundRepo(startPath: string | null): Promise<string | null> {\n if (!startPath) return null;\n let current = path.resolve(startPath);\n let stats = await fs.stat(current).catch(() => null);\n if (stats?.isFile()) {\n current = path.dirname(current);\n }\n\n while (true) {\n const bindingPath = path.join(current, \".remix\", \"config.json\");\n const bindingStats = await fs.stat(bindingPath).catch(() => null);\n if (bindingStats?.isFile()) return current;\n const parent = path.dirname(current);\n if (parent === current) return null;\n current = parent;\n }\n}\n"],"mappings":"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","names":["require","fs","path"]}
1
+ {"version":3,"sources":["../src/hook-stop-collab.ts","../src/hook-auth.ts","../src/hook-state.ts","../src/metadata.ts","../src/hook-utils.ts"],"sourcesContent":["import { collabAdd, collabRecordTurn, collabRecordingPreflight } from \"@remixhq/core/collab\";\nimport { readCollabBinding } from \"@remixhq/core/binding\";\nimport { getWorkspaceDiff } from \"@remixhq/core/repo\";\n\nimport { createHookCollabApiClient } from \"./hook-auth.js\";\nimport {\n clearPendingTurnState,\n listTouchedRepos,\n loadPendingTurnState,\n markPendingTurnFailure,\n markTouchedRepoRecordingFailure,\n markTouchedRepoStopAttempted,\n markTouchedRepoStopRecorded,\n type RepoRecordMode,\n type TouchedRepoState,\n} from \"./hook-state.js\";\nimport { pluginMetadata } from \"./metadata.js\";\nimport { extractBoolean, extractString, readJsonStdin } from \"./hook-utils.js\";\n\nconst HOOK_ACTOR = {\n type: \"agent\",\n name: \"claude-code\",\n version: pluginMetadata.version,\n provider: \"anthropic\",\n} as const;\n\nfunction getErrorDetails(error: unknown): { message: string; hint: string | null } {\n if (error instanceof Error) {\n const hint = typeof (error as { hint?: unknown }).hint === \"string\" ? String((error as { hint?: unknown }).hint) : null;\n return {\n message: error.message || \"Automatic Remix turn recording failed.\",\n hint,\n };\n }\n const message = typeof error === \"string\" && error.trim() ? error.trim() : \"Automatic Remix turn recording failed.\";\n return { message, hint: null };\n}\n\nfunction getRecordingBlockedMessage(status: Awaited<ReturnType<typeof collabRecordingPreflight>>, repoRoot: string) {\n switch (status.status) {\n case \"not_git_repo\":\n return {\n message: \"Automatic Remix turn recording failed because the repository is no longer inside a git repository.\",\n hint: status.hint || `Repo root: ${repoRoot}`,\n };\n case \"not_bound\":\n return {\n message: \"Automatic Remix turn recording failed because the repository is no longer bound to Remix.\",\n hint: status.hint || `Repo root: ${repoRoot}`,\n };\n case \"missing_head\":\n return {\n message: \"Automatic Remix turn recording failed because the repository HEAD could not be resolved.\",\n hint: status.hint || `Repo root: ${repoRoot}`,\n };\n case \"branch_mismatch\":\n return {\n message: \"Automatic Remix turn recording was blocked by the checkout's preferred-branch policy.\",\n hint: status.hint || `Repo root: ${repoRoot}`,\n };\n case \"metadata_conflict\":\n return {\n message: \"Automatic Remix turn recording was blocked because local repository metadata conflicts with the bound Remix app.\",\n hint: status.hint || `Repo root: ${repoRoot}`,\n };\n case \"reconcile_required\":\n return {\n message: \"Automatic Remix turn recording was blocked because the repository must be reconciled before recording can continue safely.\",\n hint: status.hint || `Repo root: ${repoRoot}`,\n };\n default:\n return null;\n }\n}\n\nfunction buildRepoIdempotencyKey(turnId: string, repo: TouchedRepoState, mode: RepoRecordMode): string {\n const repoToken = repo.currentAppId?.trim() || repo.repoRoot;\n return `${turnId}:${repoToken}:${mode}`;\n}\n\nfunction shouldSkipStopRecording(repo: TouchedRepoState): boolean {\n if (repo.stopRecorded) {\n return true;\n }\n if (!repo.manuallyRecorded) {\n return false;\n }\n if (!repo.manuallyRecordedAt) {\n return false;\n }\n if (!repo.lastObservedWriteAt) {\n return true;\n }\n return new Date(repo.lastObservedWriteAt).getTime() <= new Date(repo.manuallyRecordedAt).getTime();\n}\n\nasync function recordTouchedRepo(params: {\n sessionId: string;\n turnId: string;\n repo: TouchedRepoState;\n prompt: string;\n assistantResponse: string;\n api: Awaited<ReturnType<typeof createHookCollabApiClient>>;\n}): Promise<boolean> {\n const { sessionId, turnId, repo, prompt, assistantResponse, api } = params;\n await markTouchedRepoStopAttempted(sessionId, repo.repoRoot);\n\n try {\n const binding = await readCollabBinding(repo.repoRoot).catch(() => null);\n if (!binding) {\n await markTouchedRepoRecordingFailure(sessionId, repo.repoRoot, {\n message: \"Automatic Remix turn recording failed because the repository is no longer bound to Remix.\",\n hint: `Repo root: ${repo.repoRoot}`,\n });\n return false;\n }\n\n const recordingPreflight = await collabRecordingPreflight({\n api,\n cwd: repo.repoRoot,\n });\n const blocked = getRecordingBlockedMessage(recordingPreflight, repo.repoRoot);\n if (blocked) {\n await markTouchedRepoRecordingFailure(sessionId, repo.repoRoot, blocked);\n return false;\n }\n\n const workspaceDiff = await getWorkspaceDiff(repo.repoRoot);\n if (workspaceDiff.diff.trim()) {\n await collabAdd({\n api,\n cwd: repo.repoRoot,\n prompt,\n assistantResponse,\n diffSource: \"worktree\",\n idempotencyKey: buildRepoIdempotencyKey(turnId, repo, \"changed_turn\"),\n actor: HOOK_ACTOR,\n });\n await markTouchedRepoStopRecorded(sessionId, repo.repoRoot, { mode: \"changed_turn\" });\n return true;\n }\n\n await collabRecordTurn({\n api,\n cwd: repo.repoRoot,\n prompt,\n assistantResponse,\n idempotencyKey: buildRepoIdempotencyKey(turnId, repo, \"no_diff_turn\"),\n actor: HOOK_ACTOR,\n });\n await markTouchedRepoStopRecorded(sessionId, repo.repoRoot, { mode: \"no_diff_turn\" });\n return true;\n } catch (error) {\n const details = getErrorDetails(error);\n await markTouchedRepoRecordingFailure(sessionId, repo.repoRoot, details);\n return false;\n }\n}\n\nasync function main(): Promise<void> {\n const payload = await readJsonStdin();\n if (extractBoolean(payload, [\"stop_hook_active\"])) {\n return;\n }\n\n const sessionId = extractString(payload, [\"session_id\"]);\n if (!sessionId) {\n return;\n }\n\n const state = await loadPendingTurnState(sessionId);\n if (!state) {\n return;\n }\n\n try {\n const touchedRepos = await listTouchedRepos(sessionId);\n if (touchedRepos.length === 0) {\n await clearPendingTurnState(sessionId);\n return;\n }\n\n const prompt = state.prompt.trim();\n const assistantResponse = (extractString(payload, [\"last_assistant_message\"]) || \"\").trim();\n if (!prompt || !assistantResponse) {\n await markPendingTurnFailure(sessionId, {\n message: \"Automatic Remix turn recording failed because the prompt or assistant response was missing.\",\n });\n return;\n }\n\n const api = await createHookCollabApiClient();\n let hadFailure = false;\n\n for (const repo of touchedRepos) {\n if (shouldSkipStopRecording(repo)) {\n continue;\n }\n const recorded = await recordTouchedRepo({\n sessionId,\n turnId: state.turnId,\n repo,\n prompt,\n assistantResponse,\n api,\n });\n if (!recorded) {\n hadFailure = true;\n }\n }\n\n if (!hadFailure) {\n await clearPendingTurnState(sessionId);\n }\n } catch (error) {\n const details = getErrorDetails(error);\n await markPendingTurnFailure(sessionId, details);\n }\n}\n\nmain().catch((error) => {\n const message = error instanceof Error ? error.message : String(error);\n process.stderr.write(`${message}\\n`);\n process.exitCode = 0;\n});\n","import {\n createApiClient,\n createLocalSessionStore,\n createStoredSessionTokenProvider,\n createSupabaseAuthHelpers,\n resolveConfig,\n} from \"@remixhq/core\";\nimport type { CollabApiClient } from \"@remixhq/core/collab\";\n\nexport async function createHookCollabApiClient(): Promise<CollabApiClient> {\n const config = await resolveConfig();\n const sessionStore = createLocalSessionStore();\n const tokenProvider = createStoredSessionTokenProvider({\n config,\n sessionStore,\n refreshStoredSession: async ({ config: refreshConfig, session }) => {\n const supabase = createSupabaseAuthHelpers(refreshConfig);\n return supabase.refreshWithStoredSession({ session });\n },\n });\n\n return createApiClient(config, {\n tokenProvider,\n }) as unknown as CollabApiClient;\n}\n","import fs from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { randomUUID } from \"node:crypto\";\n\nimport type { TurnIntent } from \"./history-routing.js\";\n\nexport type RepoRecordMode = \"changed_turn\" | \"no_diff_turn\";\n\nexport type TouchedRepoState = {\n repoRoot: string;\n projectId: string | null;\n currentAppId: string | null;\n upstreamAppId: string | null;\n firstTouchedAt: string;\n lastTouchedAt: string;\n lastObservedWriteAt: string | null;\n touchedBy: string[];\n hasObservedWrite: boolean;\n manuallyRecorded: boolean;\n manuallyRecordedAt: string | null;\n manuallyRecordedByTool: string | null;\n stopAttempted: boolean;\n stopRecorded: boolean;\n stopRecordedAt: string | null;\n stopRecordedMode: RepoRecordMode | null;\n recordingFailureMessage: string | null;\n recordingFailureHint: string | null;\n recordingFailedAt: string | null;\n};\n\nexport type PendingTurnState = {\n sessionId: string;\n turnId: string;\n prompt: string;\n initialCwd: string | null;\n intent: TurnIntent;\n submittedAt: string;\n consultedMemory: boolean;\n touchedRepos: Record<string, TouchedRepoState>;\n turnFailureMessage: string | null;\n turnFailureHint: string | null;\n turnFailedAt: string | null;\n};\n\nfunction stateRoot(): string {\n return path.join(os.tmpdir(), \"remix-claude-plugin-hooks\");\n}\n\nfunction statePath(sessionId: string): string {\n return path.join(stateRoot(), `${sessionId}.json`);\n}\n\nfunction stateLockPath(sessionId: string): string {\n return path.join(stateRoot(), `${sessionId}.lock`);\n}\n\nfunction stateLockMetaPath(sessionId: string): string {\n return path.join(stateLockPath(sessionId), \"owner.json\");\n}\n\nasync function writeJsonAtomic(filePath: string, value: unknown): Promise<void> {\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n const tmpPath = `${filePath}.tmp-${Date.now()}-${Math.random().toString(16).slice(2)}`;\n await fs.writeFile(tmpPath, JSON.stringify(value, null, 2) + \"\\n\", \"utf8\");\n await fs.rename(tmpPath, filePath);\n}\n\nconst STATE_LOCK_WAIT_MS = 2_000;\nconst STATE_LOCK_POLL_MS = 25;\nconst STATE_LOCK_STALE_MS = 30_000;\nconst STATE_LOCK_HEARTBEAT_MS = 5_000;\n\ntype StateLockMetadata = {\n ownerId: string;\n pid: number;\n createdAt: string;\n heartbeatAt: string;\n};\n\nasync function sleep(ms: number): Promise<void> {\n await new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nasync function readStateLockMetadata(sessionId: string): Promise<StateLockMetadata | null> {\n const raw = await fs.readFile(stateLockMetaPath(sessionId), \"utf8\").catch(() => null);\n if (!raw) return null;\n try {\n const parsed = JSON.parse(raw) as Partial<StateLockMetadata>;\n if (\n typeof parsed.ownerId !== \"string\" ||\n typeof parsed.pid !== \"number\" ||\n typeof parsed.createdAt !== \"string\" ||\n typeof parsed.heartbeatAt !== \"string\"\n ) {\n return null;\n }\n return {\n ownerId: parsed.ownerId,\n pid: parsed.pid,\n createdAt: parsed.createdAt,\n heartbeatAt: parsed.heartbeatAt,\n };\n } catch {\n return null;\n }\n}\n\nasync function writeStateLockMetadata(sessionId: string, metadata: StateLockMetadata): Promise<void> {\n await writeJsonAtomic(stateLockMetaPath(sessionId), metadata);\n}\n\nasync function tryRemoveStaleStateLock(sessionId: string): Promise<boolean> {\n const lockPath = stateLockPath(sessionId);\n const metadata = await readStateLockMetadata(sessionId);\n const staleByHeartbeat =\n metadata && Date.now() - new Date(metadata.heartbeatAt).getTime() > STATE_LOCK_STALE_MS;\n if (staleByHeartbeat) {\n await fs.rm(lockPath, { recursive: true, force: true }).catch(() => undefined);\n return true;\n }\n\n if (!metadata) {\n const lockStat = await fs.stat(lockPath).catch(() => null);\n if (lockStat && Date.now() - lockStat.mtimeMs > STATE_LOCK_STALE_MS) {\n await fs.rm(lockPath, { recursive: true, force: true }).catch(() => undefined);\n return true;\n }\n }\n\n return false;\n}\n\nasync function acquireStateLock(sessionId: string): Promise<() => Promise<void>> {\n const lockPath = stateLockPath(sessionId);\n const deadline = Date.now() + STATE_LOCK_WAIT_MS;\n\n while (true) {\n try {\n await fs.mkdir(lockPath);\n const ownerId = randomUUID();\n const createdAt = new Date().toISOString();\n const metadata: StateLockMetadata = {\n ownerId,\n pid: process.pid,\n createdAt,\n heartbeatAt: createdAt,\n };\n await writeStateLockMetadata(sessionId, metadata);\n let released = false;\n const heartbeat = setInterval(() => {\n if (released) return;\n void writeStateLockMetadata(sessionId, {\n ...metadata,\n heartbeatAt: new Date().toISOString(),\n }).catch(() => undefined);\n }, STATE_LOCK_HEARTBEAT_MS);\n heartbeat.unref?.();\n\n return async () => {\n if (released) return;\n released = true;\n clearInterval(heartbeat);\n const currentMetadata = await readStateLockMetadata(sessionId);\n if (currentMetadata?.ownerId === ownerId) {\n await fs.rm(lockPath, { recursive: true, force: true }).catch(() => undefined);\n }\n };\n } catch (error) {\n const code = error && typeof error === \"object\" && \"code\" in error ? (error as { code?: unknown }).code : null;\n if (code !== \"EEXIST\") {\n throw error;\n }\n\n if (await tryRemoveStaleStateLock(sessionId)) {\n continue;\n }\n\n if (Date.now() >= deadline) {\n throw new Error(`Timed out acquiring hook state lock for session ${sessionId}.`);\n }\n await sleep(STATE_LOCK_POLL_MS);\n }\n }\n}\n\nasync function withStateLock<T>(sessionId: string, fn: () => Promise<T>): Promise<T> {\n const release = await acquireStateLock(sessionId);\n try {\n return await fn();\n } finally {\n await release();\n }\n}\n\nfunction normalizeIntent(value: unknown): TurnIntent {\n return value === \"memory_first\" || value === \"collab_state\" || value === \"git_facts\" ? value : \"neutral\";\n}\n\nfunction normalizeString(value: unknown): string | null {\n return typeof value === \"string\" && value.trim() ? value.trim() : null;\n}\n\nfunction normalizeStringArray(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return Array.from(\n new Set(\n value\n .filter((entry): entry is string => typeof entry === \"string\" && entry.trim().length > 0)\n .map((entry) => entry.trim()),\n ),\n );\n}\n\nfunction normalizeTouchedRepo(value: unknown, repoRoot: string): TouchedRepoState | null {\n if (!value || typeof value !== \"object\") return null;\n const parsed = value as Partial<TouchedRepoState>;\n const normalizedRepoRoot = normalizeString(parsed.repoRoot) ?? repoRoot.trim();\n if (!normalizedRepoRoot) return null;\n\n return {\n repoRoot: normalizedRepoRoot,\n projectId: normalizeString(parsed.projectId),\n currentAppId: normalizeString(parsed.currentAppId),\n upstreamAppId: normalizeString(parsed.upstreamAppId),\n firstTouchedAt: normalizeString(parsed.firstTouchedAt) ?? new Date().toISOString(),\n lastTouchedAt: normalizeString(parsed.lastTouchedAt) ?? new Date().toISOString(),\n lastObservedWriteAt: normalizeString(parsed.lastObservedWriteAt),\n touchedBy: normalizeStringArray(parsed.touchedBy),\n hasObservedWrite: Boolean(parsed.hasObservedWrite),\n manuallyRecorded: Boolean(parsed.manuallyRecorded),\n manuallyRecordedAt: normalizeString(parsed.manuallyRecordedAt),\n manuallyRecordedByTool: normalizeString(parsed.manuallyRecordedByTool),\n stopAttempted: Boolean(parsed.stopAttempted),\n stopRecorded: Boolean(parsed.stopRecorded),\n stopRecordedAt: normalizeString(parsed.stopRecordedAt),\n stopRecordedMode: parsed.stopRecordedMode === \"changed_turn\" || parsed.stopRecordedMode === \"no_diff_turn\" ? parsed.stopRecordedMode : null,\n recordingFailureMessage: normalizeString(parsed.recordingFailureMessage),\n recordingFailureHint: normalizeString(parsed.recordingFailureHint),\n recordingFailedAt: normalizeString(parsed.recordingFailedAt),\n };\n}\n\nfunction normalizeTouchedRepos(value: unknown): Record<string, TouchedRepoState> {\n if (!value || typeof value !== \"object\") return {};\n const entries = Object.entries(value as Record<string, unknown>)\n .map(([repoRoot, repo]) => normalizeTouchedRepo(repo, repoRoot))\n .filter((repo): repo is TouchedRepoState => repo !== null)\n .sort((a, b) => a.repoRoot.localeCompare(b.repoRoot));\n return Object.fromEntries(entries.map((repo) => [repo.repoRoot, repo]));\n}\n\nfunction createTouchedRepo(params: {\n repoRoot: string;\n projectId?: string | null;\n currentAppId?: string | null;\n upstreamAppId?: string | null;\n touchedBy?: string | null;\n hasObservedWrite?: boolean;\n}): TouchedRepoState {\n const now = new Date().toISOString();\n const touchedBy = params.touchedBy?.trim() ? [params.touchedBy.trim()] : [];\n return {\n repoRoot: params.repoRoot,\n projectId: normalizeString(params.projectId),\n currentAppId: normalizeString(params.currentAppId),\n upstreamAppId: normalizeString(params.upstreamAppId),\n firstTouchedAt: now,\n lastTouchedAt: now,\n lastObservedWriteAt: params.hasObservedWrite ? now : null,\n touchedBy,\n hasObservedWrite: Boolean(params.hasObservedWrite),\n manuallyRecorded: false,\n manuallyRecordedAt: null,\n manuallyRecordedByTool: null,\n stopAttempted: false,\n stopRecorded: false,\n stopRecordedAt: null,\n stopRecordedMode: null,\n recordingFailureMessage: null,\n recordingFailureHint: null,\n recordingFailedAt: null,\n };\n}\n\nasync function updatePendingTurnState(\n sessionId: string,\n updater: (state: PendingTurnState) => void | boolean,\n): Promise<PendingTurnState | null> {\n return withStateLock(sessionId, async () => {\n const existing = await loadPendingTurnState(sessionId);\n if (!existing) return null;\n const result = updater(existing);\n if (result === false) return existing;\n await savePendingTurnState(existing);\n return existing;\n });\n}\n\nexport async function loadPendingTurnState(sessionId: string): Promise<PendingTurnState | null> {\n const raw = await fs.readFile(statePath(sessionId), \"utf8\").catch(() => null);\n if (!raw) return null;\n try {\n const parsed = JSON.parse(raw) as Partial<PendingTurnState>;\n if (!parsed || typeof parsed !== \"object\") return null;\n if (typeof parsed.sessionId !== \"string\" || typeof parsed.turnId !== \"string\" || typeof parsed.prompt !== \"string\") {\n return null;\n }\n return {\n sessionId: parsed.sessionId,\n turnId: parsed.turnId,\n prompt: parsed.prompt,\n initialCwd: normalizeString(parsed.initialCwd),\n intent: normalizeIntent(parsed.intent),\n submittedAt: typeof parsed.submittedAt === \"string\" ? parsed.submittedAt : new Date().toISOString(),\n consultedMemory: Boolean(parsed.consultedMemory),\n touchedRepos: normalizeTouchedRepos(parsed.touchedRepos),\n turnFailureMessage: normalizeString(parsed.turnFailureMessage),\n turnFailureHint: normalizeString(parsed.turnFailureHint),\n turnFailedAt: normalizeString(parsed.turnFailedAt),\n };\n } catch {\n return null;\n }\n}\n\nexport async function savePendingTurnState(state: PendingTurnState): Promise<void> {\n await writeJsonAtomic(statePath(state.sessionId), state);\n}\n\nexport async function createPendingTurnState(params: {\n sessionId: string;\n prompt: string;\n initialCwd?: string | null;\n intent: TurnIntent;\n}): Promise<PendingTurnState> {\n return withStateLock(params.sessionId, async () => {\n const state: PendingTurnState = {\n sessionId: params.sessionId,\n turnId: randomUUID(),\n prompt: params.prompt,\n initialCwd: params.initialCwd?.trim() || null,\n intent: params.intent,\n submittedAt: new Date().toISOString(),\n consultedMemory: false,\n touchedRepos: {},\n turnFailureMessage: null,\n turnFailureHint: null,\n turnFailedAt: null,\n };\n await savePendingTurnState(state);\n return state;\n });\n}\n\nexport async function upsertTouchedRepo(\n sessionId: string,\n params: {\n repoRoot: string;\n projectId?: string | null;\n currentAppId?: string | null;\n upstreamAppId?: string | null;\n touchedBy?: string | null;\n hasObservedWrite?: boolean;\n },\n): Promise<TouchedRepoState | null> {\n const normalizedRepoRoot = params.repoRoot.trim();\n if (!normalizedRepoRoot) return null;\n const state = await updatePendingTurnState(sessionId, (existing) => {\n const current =\n existing.touchedRepos[normalizedRepoRoot] ??\n createTouchedRepo({\n repoRoot: normalizedRepoRoot,\n projectId: params.projectId,\n currentAppId: params.currentAppId,\n upstreamAppId: params.upstreamAppId,\n touchedBy: params.touchedBy,\n hasObservedWrite: params.hasObservedWrite,\n });\n\n current.projectId = normalizeString(params.projectId) ?? current.projectId;\n current.currentAppId = normalizeString(params.currentAppId) ?? current.currentAppId;\n current.upstreamAppId = normalizeString(params.upstreamAppId) ?? current.upstreamAppId;\n current.lastTouchedAt = new Date().toISOString();\n if (params.touchedBy?.trim() && !current.touchedBy.includes(params.touchedBy.trim())) {\n current.touchedBy = [...current.touchedBy, params.touchedBy.trim()].sort((a, b) => a.localeCompare(b));\n }\n if (params.hasObservedWrite) {\n current.hasObservedWrite = true;\n current.lastObservedWriteAt = new Date().toISOString();\n }\n existing.touchedRepos[normalizedRepoRoot] = current;\n });\n return state?.touchedRepos[normalizedRepoRoot] ?? null;\n}\n\nexport async function markTouchedRepoObservedWrite(\n sessionId: string,\n repoRoot: string,\n params?: { toolName?: string | null },\n): Promise<void> {\n await upsertTouchedRepo(sessionId, {\n repoRoot,\n touchedBy: params?.toolName ?? null,\n hasObservedWrite: true,\n });\n}\n\nexport async function markTouchedRepoManuallyRecorded(\n sessionId: string,\n repoRoot: string,\n params?: { toolName?: string | null },\n): Promise<void> {\n await updatePendingTurnState(sessionId, (existing) => {\n const normalizedRepoRoot = repoRoot.trim();\n if (!normalizedRepoRoot) return false;\n const current =\n existing.touchedRepos[normalizedRepoRoot] ??\n createTouchedRepo({\n repoRoot: normalizedRepoRoot,\n touchedBy: params?.toolName ?? null,\n hasObservedWrite: false,\n });\n current.lastTouchedAt = new Date().toISOString();\n current.manuallyRecorded = true;\n current.manuallyRecordedAt = new Date().toISOString();\n current.manuallyRecordedByTool = normalizeString(params?.toolName) ?? current.manuallyRecordedByTool;\n current.recordingFailureMessage = null;\n current.recordingFailureHint = null;\n current.recordingFailedAt = null;\n if (params?.toolName?.trim() && !current.touchedBy.includes(params.toolName.trim())) {\n current.touchedBy = [...current.touchedBy, params.toolName.trim()].sort((a, b) => a.localeCompare(b));\n }\n existing.touchedRepos[normalizedRepoRoot] = current;\n });\n}\n\nexport async function markPendingTurnConsultedMemory(sessionId: string): Promise<void> {\n await updatePendingTurnState(sessionId, (existing) => {\n if (existing.consultedMemory) return false;\n existing.consultedMemory = true;\n });\n}\n\nexport async function markTouchedRepoStopAttempted(sessionId: string, repoRoot: string): Promise<void> {\n await updatePendingTurnState(sessionId, (existing) => {\n const current = existing.touchedRepos[repoRoot];\n if (!current) return false;\n current.stopAttempted = true;\n current.lastTouchedAt = new Date().toISOString();\n });\n}\n\nexport async function markTouchedRepoStopRecorded(\n sessionId: string,\n repoRoot: string,\n params: {\n mode: RepoRecordMode;\n },\n): Promise<void> {\n await updatePendingTurnState(sessionId, (existing) => {\n const current = existing.touchedRepos[repoRoot];\n if (!current) return false;\n current.stopAttempted = true;\n current.stopRecorded = true;\n current.stopRecordedAt = new Date().toISOString();\n current.stopRecordedMode = params.mode;\n current.recordingFailureMessage = null;\n current.recordingFailureHint = null;\n current.recordingFailedAt = null;\n current.lastTouchedAt = new Date().toISOString();\n });\n}\n\nexport async function markTouchedRepoRecordingFailure(\n sessionId: string,\n repoRoot: string,\n params: {\n message: string;\n hint?: string | null;\n },\n): Promise<void> {\n await updatePendingTurnState(sessionId, (existing) => {\n const current = existing.touchedRepos[repoRoot];\n if (!current) return false;\n current.stopAttempted = true;\n current.recordingFailureMessage = params.message.trim();\n current.recordingFailureHint = params.hint?.trim() || null;\n current.recordingFailedAt = new Date().toISOString();\n current.lastTouchedAt = new Date().toISOString();\n });\n}\n\nexport async function markPendingTurnFailure(\n sessionId: string,\n params: {\n message: string;\n hint?: string | null;\n },\n): Promise<void> {\n await updatePendingTurnState(sessionId, (existing) => {\n existing.turnFailureMessage = params.message.trim();\n existing.turnFailureHint = params.hint?.trim() || null;\n existing.turnFailedAt = new Date().toISOString();\n });\n}\n\nexport async function listTouchedRepos(sessionId: string): Promise<TouchedRepoState[]> {\n const existing = await loadPendingTurnState(sessionId);\n if (!existing) return [];\n return Object.values(existing.touchedRepos).sort((a, b) => a.repoRoot.localeCompare(b.repoRoot));\n}\n\nexport async function clearPendingTurnState(sessionId: string): Promise<void> {\n await withStateLock(sessionId, async () => {\n await fs.rm(statePath(sessionId), { force: true }).catch(() => undefined);\n });\n}\n","import { createRequire } from \"node:module\";\n\nconst require = createRequire(import.meta.url);\nconst pkg = require(\"../package.json\") as { name: string; version: string; description: string };\n\nexport const pluginMetadata = {\n name: pkg.name,\n version: pkg.version,\n description: pkg.description,\n pluginId: \"remix\",\n agentName: \"remix-collab\",\n};\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport { readCollabBinding } from \"@remixhq/core/binding\";\n\ntype BindingSummary = {\n repoRoot: string;\n projectId: string | null;\n currentAppId: string | null;\n upstreamAppId: string | null;\n};\n\nexport async function readJsonStdin(): Promise<Record<string, unknown>> {\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) {\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));\n }\n const raw = Buffer.concat(chunks).toString(\"utf8\").trim();\n if (!raw) return {};\n try {\n const parsed = JSON.parse(raw);\n return parsed && typeof parsed === \"object\" ? (parsed as Record<string, unknown>) : {};\n } catch {\n return {};\n }\n}\n\nfunction getNestedRecord(value: unknown): Record<string, unknown> | null {\n return value && typeof value === \"object\" ? (value as Record<string, unknown>) : null;\n}\n\nexport function extractToolInput(payload: Record<string, unknown>): Record<string, unknown> {\n return getNestedRecord(payload.tool_input) ?? getNestedRecord(payload.toolInput) ?? payload;\n}\n\nexport function extractToolResponse(payload: Record<string, unknown>): Record<string, unknown> | null {\n return getNestedRecord(payload.tool_response) ?? getNestedRecord(payload.toolResponse);\n}\n\nexport function extractToolName(payload: Record<string, unknown>): string | null {\n return extractString(payload, [\"tool_name\", \"toolName\"]);\n}\n\nexport function extractString(input: Record<string, unknown>, keys: string[]): string | null {\n for (const key of keys) {\n const value = input[key];\n if (typeof value === \"string\" && value.trim()) {\n return value.trim();\n }\n }\n return null;\n}\n\nexport function extractBoolean(input: Record<string, unknown>, keys: string[]): boolean | null {\n for (const key of keys) {\n const value = input[key];\n if (typeof value === \"boolean\") {\n return value;\n }\n }\n return null;\n}\n\nexport function extractToolCwd(payload: Record<string, unknown>): string | null {\n const toolInput = extractToolInput(payload);\n return extractString(toolInput, [\"cwd\"]) ?? extractString(payload, [\"cwd\"]);\n}\n\nexport function didToolSucceed(payload: Record<string, unknown>): boolean {\n const toolResponse = extractToolResponse(payload);\n const explicitSuccess = toolResponse ? extractBoolean(toolResponse, [\"success\", \"ok\"]) : null;\n if (explicitSuccess !== null) {\n return explicitSuccess;\n }\n const hookEventName = extractString(payload, [\"hook_event_name\", \"hookEventName\"]);\n return hookEventName === \"PostToolUse\";\n}\n\nfunction collectStringPathValue(value: unknown): string[] {\n if (typeof value === \"string\" && value.trim()) return [value.trim()];\n if (Array.isArray(value)) {\n return value.flatMap((entry) => collectStringPathValue(entry));\n }\n return [];\n}\n\nfunction collectPathTargetsFromObject(input: Record<string, unknown>, keys: string[]): string[] {\n return keys.flatMap((key) => collectStringPathValue(input[key]));\n}\n\nfunction resolveCandidatePath(targetPath: string, baseDir: string): string {\n return path.isAbsolute(targetPath) ? path.normalize(targetPath) : path.resolve(baseDir, targetPath);\n}\n\nexport function extractToolPathTargets(payload: Record<string, unknown>, toolName?: string | null): string[] {\n const name = (toolName ?? extractToolName(payload) ?? \"\").trim().toLowerCase();\n const toolInput = extractToolInput(payload);\n const baseDir = extractToolCwd(payload) ?? process.cwd();\n const baseKeys = [\"path\", \"paths\", \"file_path\", \"filePath\", \"target_file\", \"targetFile\", \"filename\"];\n\n const targets =\n name === \"notebookedit\"\n ? collectPathTargetsFromObject(toolInput, [\"target_notebook\", \"notebook_path\", \"notebookPath\", ...baseKeys])\n : collectPathTargetsFromObject(toolInput, baseKeys);\n\n return Array.from(new Set(targets.map((entry) => resolveCandidatePath(entry, baseDir))));\n}\n\nexport async function findBoundRepo(startPath: string | null): Promise<string | null> {\n if (!startPath) return null;\n let current = path.resolve(startPath);\n let stats = await fs.stat(current).catch(() => null);\n if (stats?.isFile()) {\n current = path.dirname(current);\n }\n\n while (true) {\n const bindingPath = path.join(current, \".remix\", \"config.json\");\n const bindingStats = await fs.stat(bindingPath).catch(() => null);\n if (bindingStats?.isFile()) return current;\n const parent = path.dirname(current);\n if (parent === current) return null;\n current = parent;\n }\n}\n\nexport async function resolveBoundRepoSummary(startPath: string | null): Promise<BindingSummary | null> {\n const repoRoot = await findBoundRepo(startPath);\n if (!repoRoot) return null;\n const binding = await readCollabBinding(repoRoot).catch(() => null);\n if (!binding) return null;\n return {\n repoRoot,\n projectId: binding.projectId,\n currentAppId: binding.currentAppId,\n upstreamAppId: binding.upstreamAppId,\n };\n}\n\nexport async function resolveTouchedBoundReposFromPaths(paths: string[]): Promise<BindingSummary[]> {\n const resolved = await Promise.all(paths.map((targetPath) => resolveBoundRepoSummary(targetPath)));\n const unique = new Map<string, BindingSummary>();\n for (const repo of resolved) {\n if (!repo) continue;\n unique.set(repo.repoRoot, repo);\n }\n return Array.from(unique.values()).sort((a, b) => a.repoRoot.localeCompare(b.repoRoot));\n}\n\nexport async function resolveBoundRepoFromToolCwd(payload: Record<string, unknown>): Promise<BindingSummary | null> {\n return resolveBoundRepoSummary(extractToolCwd(payload));\n}\n"],"mappings":"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","names":["readCollabBinding","require","fs","path","readCollabBinding"]}