conductor-remote 1.105.0 → 1.105.1

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/dist/index.html CHANGED
@@ -25,7 +25,7 @@
25
25
  <title>Conductor Remote</title>
26
26
  <!-- Runs before the module bundle so it can catch a stale shell that fails to boot. -->
27
27
  <script src="/self-heal.js"></script>
28
- <script type="module" crossorigin src="/assets/index-FEV7llKj.js"></script>
28
+ <script type="module" crossorigin src="/assets/index-DxkIsRk2.js"></script>
29
29
  <link rel="stylesheet" crossorigin href="/assets/index-BH4EAaJZ.css">
30
30
  <link rel="manifest" href="/manifest.webmanifest"></head>
31
31
  <body>
package/dist/sw.js CHANGED
@@ -1 +1 @@
1
- if(!self.define){let e,s={};const i=(i,n)=>(i=new URL(i+".js",n).href,s[i]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=i,e.onload=s,document.head.appendChild(e)}else e=i,importScripts(i),s()}).then(()=>{let e=s[i];if(!e)throw new Error(`Module ${i} didn’t register its module`);return e}));self.define=(n,r)=>{const l=e||("document"in self?document.currentScript.src:"")||location.href;if(s[l])return;let o={};const a=e=>i(e,l),t={module:{uri:l},exports:o,require:a};s[l]=Promise.all(n.map(e=>t[e]||a(e))).then(e=>(r(...e),o))}}define(["./workbox-dcde9eb3"],function(e){"use strict";importScripts("/push-sw.js"),self.addEventListener("message",e=>{e.data&&"SKIP_WAITING"===e.data.type&&self.skipWaiting()}),e.clientsClaim(),e.precacheAndRoute([{url:"self-heal.js",revision:"49bd63adb25a09341f8d2610e8bd3c76"},{url:"push-sw.js",revision:"e7ef44deca46c0539e6ff7bba5eb815e"},{url:"index.html",revision:"09ee7881d7dfcc320a5551594fc1bda5"},{url:"assets/workbox-window.prod.es5-BBnX5xw4.js",revision:null},{url:"assets/pierre-shiki-wasm-C8ETeZem.js",revision:null},{url:"assets/pierre-light-480U9XYS.js",revision:null},{url:"assets/pierre-dark-CyvmCCZW.js",revision:null},{url:"assets/index-FEV7llKj.js",revision:null},{url:"assets/index-BH4EAaJZ.css",revision:null},{url:"assets/PierrePatch-Dc241Szr.js",revision:null},{url:"apple-touch-icon.png",revision:"1127bb396b4648add53dce3f22c92aee"},{url:"icon-192.png",revision:"c5e01ac58768627e18ee7b8b6a9239ef"},{url:"icon-512.png",revision:"a40638c55e310312457a621c9a0002c8"},{url:"icon-maskable-512.png",revision:"a9b0d962686287452216492cd2247499"},{url:"icon.svg",revision:"c1aee186821798733dd477e69a0ef243"},{url:"manifest.webmanifest",revision:"cf88fbc5755108a7fe0616fa160a8a15"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("/index.html"),{denylist:[/^\/api\//]})),e.registerRoute(({url:e})=>e.pathname.startsWith("/assets/diff-syntax/"),new e.CacheFirst({cacheName:"diff-syntax",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:31536e3})]}),"GET")});
1
+ if(!self.define){let e,s={};const i=(i,n)=>(i=new URL(i+".js",n).href,s[i]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=i,e.onload=s,document.head.appendChild(e)}else e=i,importScripts(i),s()}).then(()=>{let e=s[i];if(!e)throw new Error(`Module ${i} didn’t register its module`);return e}));self.define=(n,r)=>{const l=e||("document"in self?document.currentScript.src:"")||location.href;if(s[l])return;let o={};const a=e=>i(e,l),t={module:{uri:l},exports:o,require:a};s[l]=Promise.all(n.map(e=>t[e]||a(e))).then(e=>(r(...e),o))}}define(["./workbox-dcde9eb3"],function(e){"use strict";importScripts("/push-sw.js"),self.addEventListener("message",e=>{e.data&&"SKIP_WAITING"===e.data.type&&self.skipWaiting()}),e.clientsClaim(),e.precacheAndRoute([{url:"self-heal.js",revision:"49bd63adb25a09341f8d2610e8bd3c76"},{url:"push-sw.js",revision:"e7ef44deca46c0539e6ff7bba5eb815e"},{url:"index.html",revision:"2e84a97e338a96c9e4f0aad252d9146d"},{url:"assets/workbox-window.prod.es5-BBnX5xw4.js",revision:null},{url:"assets/pierre-shiki-wasm-C8ETeZem.js",revision:null},{url:"assets/pierre-light-480U9XYS.js",revision:null},{url:"assets/pierre-dark-CyvmCCZW.js",revision:null},{url:"assets/index-DxkIsRk2.js",revision:null},{url:"assets/index-BH4EAaJZ.css",revision:null},{url:"assets/PierrePatch-D6dtPD_v.js",revision:null},{url:"apple-touch-icon.png",revision:"1127bb396b4648add53dce3f22c92aee"},{url:"icon-192.png",revision:"c5e01ac58768627e18ee7b8b6a9239ef"},{url:"icon-512.png",revision:"a40638c55e310312457a621c9a0002c8"},{url:"icon-maskable-512.png",revision:"a9b0d962686287452216492cd2247499"},{url:"icon.svg",revision:"c1aee186821798733dd477e69a0ef243"},{url:"manifest.webmanifest",revision:"cf88fbc5755108a7fe0616fa160a8a15"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("/index.html"),{denylist:[/^\/api\//]})),e.registerRoute(({url:e})=>e.pathname.startsWith("/assets/diff-syntax/"),new e.CacheFirst({cacheName:"diff-syntax",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:31536e3})]}),"GET")});
@@ -1,4 +1,5 @@
1
1
  import { execFile } from 'node:child_process';
2
+ import path from 'node:path';
2
3
  import { promisify } from 'node:util';
3
4
  import { isPreviewableSource } from "./shared.js";
4
5
  const exec = promisify(execFile);
@@ -12,6 +13,20 @@ async function git(cwd, args) {
12
13
  });
13
14
  return stdout;
14
15
  }
16
+ /** `git diff --no-index` reports an ordinary difference with exit code 1. */
17
+ async function noIndexDiff(cwd, file) {
18
+ try {
19
+ const { stdout } = await exec('git', ['-C', cwd, 'diff', '--no-index', '--no-color', '--', '/dev/null', file], {
20
+ encoding: 'utf8',
21
+ maxBuffer: 8 * 1024 * 1024,
22
+ timeout: 10000
23
+ });
24
+ return stdout;
25
+ }
26
+ catch (err) {
27
+ return err.stdout ?? '';
28
+ }
29
+ }
15
30
  /**
16
31
  * Patch for untracked files. `git diff` ignores them, but a reviewer wants to
17
32
  * see new files — so we synthesize a "new file" diff via `--no-index` against
@@ -30,22 +45,12 @@ async function untrackedDiff(cwd) {
30
45
  const files = [];
31
46
  const patches = [];
32
47
  for (const p of paths) {
33
- try {
34
- // --no-index exits 1 when files differ, so read stdout from the error.
35
- await exec('git', ['-C', cwd, 'diff', '--no-index', '--no-color', '--', '/dev/null', p], {
36
- encoding: 'utf8',
37
- maxBuffer: 4 * 1024 * 1024,
38
- timeout: 10000
39
- });
40
- }
41
- catch (err) {
42
- const out = err.stdout ?? '';
43
- if (!out)
44
- continue;
45
- const added = out.split('\n').filter(l => l.startsWith('+') && !l.startsWith('+++')).length;
46
- files.push({ path: p, added, removed: 0 });
47
- patches.push(out);
48
- }
48
+ const out = await noIndexDiff(cwd, p);
49
+ if (!out)
50
+ continue;
51
+ const added = out.split('\n').filter(l => l.startsWith('+') && !l.startsWith('+++')).length;
52
+ files.push({ path: p, added, removed: 0 });
53
+ patches.push(out);
49
54
  }
50
55
  return { files, patch: patches.join('') };
51
56
  }
@@ -159,6 +164,38 @@ export async function workspaceDiff(worktree, base) {
159
164
  const { dirty, unpushed } = await localState(worktree);
160
165
  return { base: ref, mergeBase, files, patch, truncated, dirty, unpushed };
161
166
  }
167
+ /**
168
+ * The complete patch for one selected changed file. The phone asks for this separately
169
+ * so a file remains reviewable even when its section follows the aggregate 400 KB cap.
170
+ */
171
+ export async function workspaceFileDiff(worktree, base, requestedPath) {
172
+ if (!requestedPath || requestedPath.includes('\0') || path.isAbsolute(requestedPath))
173
+ return null;
174
+ const root = path.resolve(worktree);
175
+ const relative = path.relative(root, path.resolve(root, requestedPath));
176
+ if (!relative || relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative))
177
+ return null;
178
+ const { against } = await diffBasis(worktree, base);
179
+ // A literal pathspec prevents a filename beginning with `:` from becoming Git pathspec syntax.
180
+ const literalPathspec = `:(literal)${relative}`;
181
+ const tracked = await git(worktree, ['diff', '--no-color', against, '--', literalPathspec]).catch(() => '');
182
+ if (tracked)
183
+ return { path: relative, patch: tracked };
184
+ // `git diff <base>` omits untracked files. Only synthesize a patch when Git says this
185
+ // exact path is untracked and not ignored; never let the request name an arbitrary file.
186
+ const untracked = await git(worktree, [
187
+ 'ls-files',
188
+ '--others',
189
+ '--exclude-standard',
190
+ '-z',
191
+ '--',
192
+ literalPathspec
193
+ ]).catch(() => '');
194
+ if (!untracked.split('\0').includes(relative))
195
+ return null;
196
+ const patch = await noIndexDiff(worktree, relative);
197
+ return patch ? { path: relative, patch } : null;
198
+ }
162
199
  /**
163
200
  * Local publish state of the worktree: does it have uncommitted changes, and are
164
201
  * there commits not yet on its remote-tracking branch? Together these drive the
@@ -108,6 +108,8 @@ export const routes = {
108
108
  sessions: param('GET', '/api/workspaces/:workspaceId/sessions'),
109
109
  newChat: param('POST', '/api/workspaces/:workspaceId/sessions'),
110
110
  diff: param('GET', '/api/workspaces/:workspaceId/diff'),
111
+ /** One complete changed-file patch, addressed by the `path` query parameter. */
112
+ fileDiff: param('GET', '/api/workspaces/:workspaceId/diff/file'),
111
113
  /** Previewable worktree files for chat links and the diff window's All-files rail. */
112
114
  workspaceFiles: param('GET', '/api/workspaces/:workspaceId/files'),
113
115
  merge: param('POST', '/api/workspaces/:workspaceId/merge'),
@@ -17,7 +17,7 @@ import { isAllowedPreviewPath, parseFileReference, parseImageReference } from ".
17
17
  import { FirstPromptQueue } from "./firstprompt.js";
18
18
  import { captureForkWorkspace, materializeForkWorkspace, releaseForkWorkspace } from "./fork-workspace.js";
19
19
  import { startFunnelWatchdog } from "./funnel-watchdog.js";
20
- import { listSourceFiles, workspaceDiff } from "./git.js";
20
+ import { listSourceFiles, workspaceDiff, workspaceFileDiff } from "./git.js";
21
21
  import { installLogCapture, isManaged, LOG_FILE_NAMES, logFiles, processStartedAt, recentLogs, redactSecrets, tailLogFile } from "./logbuf.js";
22
22
  import { createTools, handleRpc, READ_TIMEOUT_MS } from "./mcp-tools.js";
23
23
  import { mergePr } from "./merge.js";
@@ -2103,6 +2103,24 @@ const server = http.createServer(async (req, res) => {
2103
2103
  const diff = await workspaceDiff(ws.worktree, ws.baseBranch);
2104
2104
  return json(req, res, 200, diff);
2105
2105
  }
2106
+ // GET /api/workspaces/:id/diff/file?path=… — the complete patch for the file
2107
+ // currently on screen. The aggregate endpoint stays bounded for phone-sized
2108
+ // responses, while a late file no longer disappears behind that bound.
2109
+ const fileDiffOf = routeParam(routes.fileDiff, req.method, pathname);
2110
+ if (fileDiffOf) {
2111
+ const ws = reads.getWorkspace(fileDiffOf);
2112
+ if (!ws)
2113
+ return json(req, res, 404, { error: 'workspace not found' });
2114
+ if (!ws.worktree)
2115
+ return json(req, res, 409, { error: 'worktree path unresolved' });
2116
+ const file = url.searchParams.get('path');
2117
+ if (!file)
2118
+ return json(req, res, 400, { error: 'file path is required' });
2119
+ const diff = await workspaceFileDiff(ws.worktree, ws.baseBranch, file);
2120
+ if (!diff)
2121
+ return json(req, res, 404, { error: 'changed file not found' });
2122
+ return json(req, res, 200, diff);
2123
+ }
2106
2124
  // GET /api/workspaces/:id/files — previewable worktree files for the diff window's
2107
2125
  // All-files rail and for linking `tests/foo.ts` in a message only when it really exists.
2108
2126
  // A workspace with no worktree has no list for either caller.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "conductor-remote",
3
- "version": "1.105.0",
3
+ "version": "1.105.1",
4
4
  "type": "module",
5
5
  "packageManager": "yarn@4.15.0",
6
6
  "description": "Phone control panel for local Conductor agents. Reads ride SQLite + git; prompts ride Conductor's own dispatch path.",