@parall/cli 1.19.0 → 1.20.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.
@@ -1 +1 @@
1
- {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/commands/mcp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAmBpC,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,QA6bnD"}
1
+ {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/commands/mcp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAoBpC,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,QAicnD"}
@@ -1,8 +1,8 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
3
  import * as z from 'zod/v4';
4
- import { resolveCredentials } from '../lib/client.js';
5
- import { getWikiBlob, getWikiChangesetDiff, getWikiOutline, queryWiki, getWikiSection, getWikiTree, getWikiWorkspaceDiff, getWikiWorkspaceStatus, listWikis, proposeWikiChangeset, searchWiki, } from '../lib/wiki.js';
4
+ import { resolveCredentials, resolveRuntimeContext } from '../lib/client.js';
5
+ import { getWikiBlob, getWikiChangesetDiff, getWikiOutline, backfillProposedChangeset, queryWiki, getWikiSection, getWikiTree, getWikiWorkspaceDiff, getWikiWorkspaceStatus, listWikis, proposeWikiChangeset, searchWiki, } from '../lib/wiki.js';
6
6
  export function registerMcpCommands(program) {
7
7
  program
8
8
  .command('mcp')
@@ -419,13 +419,17 @@ export function registerMcpCommands(program) {
419
419
  }).passthrough(),
420
420
  },
421
421
  }, async ({ wiki, title, message, changeset_id: changesetId, source_chat_id: sourceChatId, source_message_id: sourceMessageId, source_run_id: sourceRunId }) => {
422
+ const runtime = resolveRuntimeContext();
422
423
  const result = await proposeWikiChangeset(ctx, wiki, {
423
424
  title,
424
425
  message,
425
426
  changesetId,
426
- sourceChatId,
427
- sourceMessageId,
428
- sourceRunId,
427
+ sourceChatId: sourceChatId ?? runtime.chatId,
428
+ sourceMessageId: sourceMessageId ?? runtime.triggerMessageId,
429
+ sourceRunId: sourceRunId ?? runtime.sessionId,
430
+ });
431
+ await backfillProposedChangeset(ctx, runtime, result.changeset, {
432
+ includeComment: !changesetId,
429
433
  });
430
434
  return {
431
435
  content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
@@ -17,9 +17,9 @@ export function registerMessageCommands(program) {
17
17
  const { client, orgId } = resolveCredentials();
18
18
  const params = { limit: Number(opts.limit) };
19
19
  if (opts.before !== undefined)
20
- params.before = opts.before;
20
+ params.before = stripPrllScheme(opts.before);
21
21
  if (opts.after !== undefined)
22
- params.after = opts.after;
22
+ params.after = stripPrllScheme(opts.after);
23
23
  if (opts.threadRootId !== undefined)
24
24
  params.thread_root_id = stripPrllScheme(opts.threadRootId);
25
25
  if (opts.topLevel)
@@ -112,9 +112,9 @@ export function registerMessageCommands(program) {
112
112
  const { client } = resolveCredentials();
113
113
  const params = { limit: Number(opts.limit) };
114
114
  if (opts.before !== undefined)
115
- params.before = opts.before;
115
+ params.before = stripPrllScheme(opts.before);
116
116
  if (opts.after !== undefined)
117
- params.after = opts.after;
117
+ params.after = stripPrllScheme(opts.after);
118
118
  const result = await client.getMessageReplies(stripPrllScheme(messageId), params);
119
119
  printJson(result);
120
120
  }
@@ -1 +1 @@
1
- {"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../src/commands/projects.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKpC,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,QA6FvD"}
1
+ {"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../src/commands/projects.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKpC,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,QA8FvD"}
@@ -1,5 +1,5 @@
1
1
  import { resolveCredentials } from '../lib/client.js';
2
- import { printJson, printError } from '../lib/output.js';
2
+ import { printJson, printError, stripPrllScheme } from '../lib/output.js';
3
3
  export function registerProjectCommands(program) {
4
4
  const projects = program.command('projects').description('Manage projects');
5
5
  projects
@@ -22,7 +22,7 @@ export function registerProjectCommands(program) {
22
22
  .action(async (projectId) => {
23
23
  try {
24
24
  const { client, orgId } = resolveCredentials();
25
- const result = await client.getProject(orgId, projectId);
25
+ const result = await client.getProject(orgId, stripPrllScheme(projectId));
26
26
  printJson(result);
27
27
  }
28
28
  catch (err) {
@@ -80,7 +80,7 @@ export function registerProjectCommands(program) {
80
80
  if (Object.keys(data).length === 0) {
81
81
  printError(new Error('No fields to update. Provide at least one option.'));
82
82
  }
83
- const result = await client.updateProject(orgId, projectId, data);
83
+ const result = await client.updateProject(orgId, stripPrllScheme(projectId), data);
84
84
  printJson(result);
85
85
  }
86
86
  catch (err) {
@@ -94,8 +94,9 @@ export function registerProjectCommands(program) {
94
94
  .action(async (projectId) => {
95
95
  try {
96
96
  const { client, orgId } = resolveCredentials();
97
- await client.deleteProject(orgId, projectId);
98
- printJson({ deleted: projectId });
97
+ const id = stripPrllScheme(projectId);
98
+ await client.deleteProject(orgId, id);
99
+ printJson({ deleted: id });
99
100
  }
100
101
  catch (err) {
101
102
  printError(err);
@@ -1,5 +1,5 @@
1
1
  import { resolveCredentials } from '../lib/client.js';
2
- import { printJson, printError } from '../lib/output.js';
2
+ import { printJson, printError, stripPrllScheme } from '../lib/output.js';
3
3
  export function registerUserCommands(program) {
4
4
  // whoami — registered directly on program
5
5
  program
@@ -24,7 +24,7 @@ export function registerUserCommands(program) {
24
24
  .action(async (userId) => {
25
25
  try {
26
26
  const { client } = resolveCredentials();
27
- const result = await client.getUser(userId);
27
+ const result = await client.getUser(stripPrllScheme(userId));
28
28
  printJson(result);
29
29
  }
30
30
  catch (err) {
@@ -1 +1 @@
1
- {"version":3,"file":"wiki.d.ts","sourceRoot":"","sources":["../../src/commands/wiki.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAwBpC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,QAwwBpD"}
1
+ {"version":3,"file":"wiki.d.ts","sourceRoot":"","sources":["../../src/commands/wiki.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA0BpC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,QAmxBpD"}
@@ -1,6 +1,6 @@
1
1
  import { resolveCredentials, resolveRuntimeContext } from '../lib/client.js';
2
2
  import { printError, printJson } from '../lib/output.js';
3
- import { getAfcsDiff, getAfcsStatus, getWikiBlob, getWikiChangesetDetail, getWikiChangesetDiff, getWikiLog, getWikiOutline, getWikiSection, getWikiTree, listWikiChangesets, queryWiki, requestWikiAccess, resetWikiWorkspace, listWikis, proposeWikiChangeset, searchWiki, syncAllMounts, watchMounts, } from '../lib/wiki.js';
3
+ import { getAfcsDiff, getAfcsStatus, getWikiBlob, getWikiChangesetDetail, getWikiChangesetDiff, getWikiLog, getWikiOutline, getWikiSection, getWikiTree, listWikiChangesets, queryWiki, requestWikiAccess, resetWikiWorkspace, backfillProposedChangeset, backfillMergedChangeset, listWikis, proposeWikiChangeset, searchWiki, syncAllMounts, watchMounts, } from '../lib/wiki.js';
4
4
  export function registerWikiCommands(program) {
5
5
  const wiki = program.command('wiki').description('Parall Wiki — read, edit, and propose changes');
6
6
  // ---- CRUD ----
@@ -41,6 +41,12 @@ export function registerWikiCommands(program) {
41
41
  process.stderr.write(`wiki ${entry.slug} synced → ${entry.path}\n`);
42
42
  }
43
43
  printJson(result);
44
+ // Conflicts are an expected, agent-resolvable state; failures (download
45
+ // error, shape conflict, etc.) are not. Reflect that in the exit code
46
+ // so CI / scripts that check status see partial sync as failure.
47
+ if (!result.ok) {
48
+ process.exitCode = 1;
49
+ }
44
50
  }
45
51
  catch (error) {
46
52
  printError(error);
@@ -203,6 +209,9 @@ export function registerWikiCommands(program) {
203
209
  sourceMessageId: options.messageId ?? rtx.triggerMessageId,
204
210
  sourceRunId: options.sessionId ?? rtx.sessionId,
205
211
  });
212
+ await backfillProposedChangeset(ctx, rtx, result.changeset, {
213
+ includeComment: !options.update,
214
+ });
206
215
  const verb = options.update ? 'Re-proposed' : 'Created';
207
216
  process.stderr.write(`${verb} changeset ${result.changeset.id}\n`);
208
217
  process.stderr.write(`Changed files: ${result.changed_paths.join(', ')}\n`);
@@ -303,9 +312,11 @@ export function registerWikiCommands(program) {
303
312
  .action(async (changesetId, wikiRef) => {
304
313
  try {
305
314
  const ctx = resolveCredentials();
315
+ const rtx = resolveRuntimeContext();
306
316
  const { resolveWikiRefOrDefault } = await import('../lib/wiki.js');
307
317
  const wiki = await resolveWikiRefOrDefault(ctx, wikiRef);
308
318
  const result = await ctx.client.mergeWikiChangeset(ctx.orgId, wiki.id, changesetId);
319
+ await backfillMergedChangeset(ctx, rtx, result);
309
320
  process.stderr.write(`Merged changeset ${result.id} — "${result.title}"\n`);
310
321
  printJson(result);
311
322
  }
@@ -1,4 +1,5 @@
1
1
  import { type ParallClient, type Wiki, type WikiChangeset, type WikiDiff, type WikiDiffFile, type WikiTreeEntry } from '@parall/sdk';
2
+ import type { RuntimeContext } from './client.js';
2
3
  export type ParallContext = {
3
4
  client: ParallClient;
4
5
  orgId: string;
@@ -119,6 +120,15 @@ export type WikiProposeResult = {
119
120
  changeset: WikiChangeset;
120
121
  source: 'local_mount';
121
122
  };
123
+ export type ChangesetBackfillMode = 'proposed' | 'merged';
124
+ export type ChangesetTaskBackfillAttempt = {
125
+ task_id: string;
126
+ relation_created: boolean;
127
+ comment_created: boolean;
128
+ };
129
+ export type ChangesetTaskBackfillResult = {
130
+ attempts: ChangesetTaskBackfillAttempt[];
131
+ };
122
132
  type WikiContentSource = 'local_mount' | 'api';
123
133
  export declare function listWikis(ctx: ParallContext): Promise<Wiki[]>;
124
134
  export declare function resolveWikiRef(ctx: ParallContext, wikiRef?: string): Promise<Wiki>;
@@ -194,6 +204,11 @@ export type AfcsDiffResult = {
194
204
  };
195
205
  export declare function getAfcsDiff(ctx: ParallContext, wikiRef?: string): Promise<AfcsDiffResult>;
196
206
  export declare function proposeWikiChangeset(ctx: ParallContext, wikiRef: string | undefined, options: WikiProposeOptions): Promise<WikiProposeResult>;
207
+ export declare function backfillProposedChangeset(ctx: ParallContext, runtime: RuntimeContext, changeset: WikiChangeset, options?: {
208
+ relationType?: 'reference' | 'execution';
209
+ includeComment?: boolean;
210
+ }): Promise<ChangesetTaskBackfillResult>;
211
+ export declare function backfillMergedChangeset(ctx: ParallContext, runtime: RuntimeContext, changeset: WikiChangeset): Promise<ChangesetTaskBackfillResult>;
197
212
  /**
198
213
  * Discard all local changes and restore files to the last synced state.
199
214
  * Reads the local manifest and restores all files from the server.
@@ -245,11 +260,19 @@ export type AfcsLogResult = {
245
260
  };
246
261
  export declare function getWikiLog(ctx: ParallContext, wikiRef?: string, filePath?: string): Promise<AfcsLogResult>;
247
262
  export type SyncResult = {
248
- ok: true;
263
+ /** True iff every wiki's per-path action ran without failure. Conflicts do
264
+ * NOT clear this flag — they are an expected, structured outcome the agent
265
+ * is responsible for resolving. Only `failed[]` (download error,
266
+ * shape-conflict, etc.) flips ok to false. */
267
+ ok: boolean;
249
268
  mounts: number;
250
269
  synced: {
251
270
  slug: string;
252
271
  path: string;
272
+ applied: number;
273
+ fastForwarded: number;
274
+ conflicts: SyncConflict[];
275
+ failed: SyncFailure[];
253
276
  }[];
254
277
  };
255
278
  /**
@@ -265,5 +288,96 @@ export declare function syncAllMounts(ctx: ParallContext): Promise<SyncResult>;
265
288
  export declare function watchMounts(ctx: ParallContext, intervalSec: number): {
266
289
  stop: () => void;
267
290
  };
291
+ /**
292
+ * Sync a single wiki:
293
+ * 1. Fetch server manifest (file listing with SHAs)
294
+ * 2. Compare with local manifest
295
+ * 3. Download new/modified files via bulk download
296
+ * 4. Delete removed files
297
+ * 5. Save updated local manifest
298
+ */
299
+ /** Per-path sync action computed by three-way comparison. */
300
+ export type SyncAction = {
301
+ kind: 'noop';
302
+ path: string;
303
+ } | {
304
+ kind: 'apply-write';
305
+ path: string;
306
+ remoteSha: string;
307
+ remoteSize: number;
308
+ } | {
309
+ kind: 'apply-delete';
310
+ path: string;
311
+ } | {
312
+ kind: 'fast-forward';
313
+ path: string;
314
+ remoteSha: string;
315
+ remoteSize: number;
316
+ } | {
317
+ kind: 'fast-forward-delete';
318
+ path: string;
319
+ } | {
320
+ kind: 'conflict-write';
321
+ path: string;
322
+ remoteSha: string;
323
+ remoteSize: number;
324
+ baseSha: string | null;
325
+ localSha: string | null;
326
+ } | {
327
+ kind: 'conflict-delete';
328
+ path: string;
329
+ baseSha: string;
330
+ };
331
+ /**
332
+ * Pure three-way sync planner. Decides what to do per-path given the base
333
+ * (manifest entry from last successful sync), the current local working tree
334
+ * sha, and the latest remote sha. No I/O.
335
+ */
336
+ export declare function planSyncActions(input: {
337
+ paths: Iterable<string>;
338
+ baseSha: (path: string) => string | null;
339
+ localSha: (path: string) => string | null;
340
+ remote: (path: string) => {
341
+ sha: string;
342
+ size: number;
343
+ } | null;
344
+ }): SyncAction[];
345
+ export type SyncConflict = {
346
+ path: string;
347
+ /** Where the upstream copy was written (or null for remote-deleted markers). */
348
+ remoteCopyPath: string | null;
349
+ /** Type of conflict for logging / UI surface. */
350
+ reason: 'remote-changed-local-edited' | 'remote-deleted-local-edited' | 'remote-added-local-collides' | 'remote-changed-local-deleted';
351
+ };
352
+ export type SyncFailure = {
353
+ path: string;
354
+ /**
355
+ * Reason classification:
356
+ * - 'download' — bulk-download endpoint surfaced a per-file error.
357
+ * - 'shape-conflict' — local working tree has an incompatible file/directory
358
+ * shape at this path (e.g. local `foo` is a directory
359
+ * while remote treats `foo` as a file, or vice versa
360
+ * when remote is `foo/bar.md` but local has `foo` as
361
+ * a regular file).
362
+ */
363
+ reason: 'download' | 'shape-conflict';
364
+ /** Server-supplied error string or local errno, when available. */
365
+ detail?: string;
366
+ };
367
+ export type WikiSyncOutcome = {
368
+ applied: number;
369
+ fastForwarded: number;
370
+ conflicts: SyncConflict[];
371
+ /** Per-path failures that prevented a planned action from running. Manifest is left unchanged for these — next sync retries. */
372
+ failed: SyncFailure[];
373
+ };
374
+ export declare function syncSingleWiki(ctx: ParallContext, wiki: import('@parall/sdk').Wiki, mountPath: string): Promise<WikiSyncOutcome>;
375
+ /**
376
+ * Write a sync conflict marker under .parall-wiki/conflicts/, mirroring the
377
+ * source path so the agent (or user) can locate the upstream copy next to the
378
+ * local edit. Returns the absolute path written.
379
+ */
380
+ export declare function writeConflictMarker(mountRoot: string, sourcePath: string, suffix: string, content: Buffer): Promise<string>;
381
+ export declare function computeGitBlobSHA(content: Buffer): string;
268
382
  export {};
269
383
  //# sourceMappingURL=wiki.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"wiki.d.ts","sourceRoot":"","sources":["../../src/lib/wiki.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"wiki.d.ts","sourceRoot":"","sources":["../../src/lib/wiki.ts"],"names":[],"mappings":"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"}
package/dist/lib/wiki.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { promises as fs } from 'node:fs';
3
3
  import path from 'node:path';
4
- import { ApiError, } from '@parall/sdk';
4
+ import { ApiError, COMMENT_TARGET, } from '@parall/sdk';
5
5
  // ---------------------------------------------------------------------------
6
6
  // Constants
7
7
  // ---------------------------------------------------------------------------
@@ -10,6 +10,9 @@ const TRAILING_FENCE_RE = /\s+#+\s*$/;
10
10
  const LOCAL_NODE_SECTION_ARTIFACT_VERSION = 2;
11
11
  const LOCAL_MANIFEST_FILE = 'manifest.json';
12
12
  const PRLL_WIKI_DIR = '.parall-wiki';
13
+ const CONFLICTS_DIR = 'conflicts';
14
+ const REMOTE_CONFLICT_SUFFIX = '.remote';
15
+ const REMOTE_DELETED_MARKER_SUFFIX = '.remote-deleted';
13
16
  // ---------------------------------------------------------------------------
14
17
  // Wiki resolution helpers
15
18
  // ---------------------------------------------------------------------------
@@ -347,6 +350,96 @@ export async function proposeWikiChangeset(ctx, wikiRef, options) {
347
350
  source: 'local_mount',
348
351
  };
349
352
  }
353
+ // backfillProposedChangeset attaches the current runtime's task to a
354
+ // freshly-proposed changeset: one relation (task → changeset) and a no_reply
355
+ // "Proposed" comment on the task. Best-effort: failures are logged and
356
+ // swallowed so the propose action itself stays authoritative.
357
+ export async function backfillProposedChangeset(ctx, runtime, changeset, options = {}) {
358
+ const step = await resolveRuntimeTaskStep(ctx, runtime);
359
+ if (!step?.target_id) {
360
+ return { attempts: [] };
361
+ }
362
+ const taskId = step.target_id;
363
+ const sessionId = normalizeOptionalString(runtime.sessionId);
364
+ const agentId = resolveAgentId(ctx.agentId);
365
+ const attempt = {
366
+ task_id: taskId,
367
+ relation_created: false,
368
+ comment_created: false,
369
+ };
370
+ try {
371
+ await ctx.client.createTaskRelation(ctx.orgId, taskId, {
372
+ target_type: 'wiki_changeset',
373
+ target_id: changeset.id,
374
+ relation_type: options.relationType ?? 'reference',
375
+ });
376
+ attempt.relation_created = true;
377
+ }
378
+ catch (err) {
379
+ process.stderr.write(`warning: changeset ${changeset.id} relation backfill to task ${taskId} failed: ${formatBackfillError(err)}\n`);
380
+ }
381
+ if (options.includeComment !== false && sessionId && agentId) {
382
+ try {
383
+ await ctx.client.createAgentTaskBackfillComment(ctx.orgId, agentId, sessionId, {
384
+ task_id: taskId,
385
+ body: buildChangesetTaskComment(changeset, 'proposed'),
386
+ });
387
+ attempt.comment_created = true;
388
+ }
389
+ catch (err) {
390
+ process.stderr.write(`warning: changeset ${changeset.id} "Proposed" comment backfill to task ${taskId} failed: ${formatBackfillError(err)}\n`);
391
+ }
392
+ }
393
+ return { attempts: [attempt] };
394
+ }
395
+ // backfillMergedChangeset posts a no_reply "Merged" comment on every task that
396
+ // was linked to the changeset at propose time. It does NOT consult the
397
+ // merging runtime's current task: that task may be unrelated to the
398
+ // changeset, and recording the merge there would leak wrong context.
399
+ // If no task was ever linked (e.g. changeset proposed via UI/human), this
400
+ // quietly does nothing.
401
+ export async function backfillMergedChangeset(ctx, runtime, changeset) {
402
+ const sessionId = normalizeOptionalString(runtime.sessionId);
403
+ const agentId = resolveAgentId(ctx.agentId);
404
+ if (!sessionId || !agentId) {
405
+ return { attempts: [] };
406
+ }
407
+ let relations;
408
+ try {
409
+ relations = await ctx.client.listTaskRelationsByTarget(ctx.orgId, {
410
+ target_type: 'wiki_changeset',
411
+ target_id: changeset.id,
412
+ });
413
+ }
414
+ catch (err) {
415
+ process.stderr.write(`warning: changeset ${changeset.id} merge backfill could not list task relations: ${formatBackfillError(err)}\n`);
416
+ return { attempts: [] };
417
+ }
418
+ const taskIds = Array.from(new Set(relations.map((r) => r.task_id)));
419
+ if (taskIds.length === 0) {
420
+ return { attempts: [] };
421
+ }
422
+ const attempts = [];
423
+ for (const taskId of taskIds) {
424
+ const attempt = {
425
+ task_id: taskId,
426
+ relation_created: true, // relation already exists — that's how we found this task
427
+ comment_created: false,
428
+ };
429
+ try {
430
+ await ctx.client.createAgentTaskBackfillComment(ctx.orgId, agentId, sessionId, {
431
+ task_id: taskId,
432
+ body: buildChangesetTaskComment(changeset, 'merged'),
433
+ });
434
+ attempt.comment_created = true;
435
+ }
436
+ catch (err) {
437
+ process.stderr.write(`warning: changeset ${changeset.id} "Merged" comment backfill to task ${taskId} failed: ${formatBackfillError(err)}\n`);
438
+ }
439
+ attempts.push(attempt);
440
+ }
441
+ return { attempts };
442
+ }
350
443
  // ---------------------------------------------------------------------------
351
444
  // AFCS: reset workspace
352
445
  // ---------------------------------------------------------------------------
@@ -473,11 +566,19 @@ export async function syncAllMounts(ctx) {
473
566
  for (const wiki of wikis) {
474
567
  const mountPath = resolveMountPath(wiki.slug, mountRoot);
475
568
  activeMountPaths.add(mountPath);
476
- await syncSingleWiki(ctx, wiki, mountPath);
477
- synced.push({ slug: wiki.slug, path: mountPath });
569
+ const outcome = await syncSingleWiki(ctx, wiki, mountPath);
570
+ synced.push({
571
+ slug: wiki.slug,
572
+ path: mountPath,
573
+ applied: outcome.applied,
574
+ fastForwarded: outcome.fastForwarded,
575
+ conflicts: outcome.conflicts,
576
+ failed: outcome.failed,
577
+ });
478
578
  }
479
579
  await pruneStaleMounts(mountRoot, activeMountPaths);
480
- return { ok: true, mounts: wikis.length, synced };
580
+ const ok = synced.every((s) => s.failed.length === 0);
581
+ return { ok, mounts: wikis.length, synced };
481
582
  }
482
583
  /**
483
584
  * Run {@link syncAllMounts} in a loop, sleeping `intervalSec` between
@@ -505,18 +606,50 @@ export function watchMounts(ctx, intervalSec) {
505
606
  process.on('SIGTERM', stop);
506
607
  return { stop };
507
608
  }
508
- // ---------------------------------------------------------------------------
509
- // Sync internals (REST-based)
510
- // ---------------------------------------------------------------------------
511
609
  /**
512
- * Sync a single wiki:
513
- * 1. Fetch server manifest (file listing with SHAs)
514
- * 2. Compare with local manifest
515
- * 3. Download new/modified files via bulk download
516
- * 4. Delete removed files
517
- * 5. Save updated local manifest
610
+ * Pure three-way sync planner. Decides what to do per-path given the base
611
+ * (manifest entry from last successful sync), the current local working tree
612
+ * sha, and the latest remote sha. No I/O.
518
613
  */
519
- async function syncSingleWiki(ctx, wiki, mountPath) {
614
+ export function planSyncActions(input) {
615
+ const actions = [];
616
+ for (const p of input.paths) {
617
+ const baseSha = input.baseSha(p);
618
+ const remote = input.remote(p);
619
+ const remoteSha = remote?.sha ?? null;
620
+ const localSha = input.localSha(p);
621
+ if (baseSha === remoteSha) {
622
+ actions.push({ kind: 'noop', path: p });
623
+ continue;
624
+ }
625
+ const cleanLocal = localSha === baseSha;
626
+ if (cleanLocal) {
627
+ if (remoteSha === null) {
628
+ actions.push({ kind: 'apply-delete', path: p });
629
+ }
630
+ else {
631
+ actions.push({ kind: 'apply-write', path: p, remoteSha, remoteSize: remote.size });
632
+ }
633
+ continue;
634
+ }
635
+ if (localSha !== null && localSha === remoteSha) {
636
+ actions.push({ kind: 'fast-forward', path: p, remoteSha, remoteSize: remote.size });
637
+ continue;
638
+ }
639
+ if (localSha === null && remoteSha === null) {
640
+ actions.push({ kind: 'fast-forward-delete', path: p });
641
+ continue;
642
+ }
643
+ if (remoteSha === null) {
644
+ actions.push({ kind: 'conflict-delete', path: p, baseSha: baseSha });
645
+ }
646
+ else {
647
+ actions.push({ kind: 'conflict-write', path: p, remoteSha, remoteSize: remote.size, baseSha, localSha });
648
+ }
649
+ }
650
+ return actions;
651
+ }
652
+ export async function syncSingleWiki(ctx, wiki, mountPath) {
520
653
  await fs.mkdir(mountPath, { recursive: true });
521
654
  const metaDir = path.join(mountPath, PRLL_WIKI_DIR);
522
655
  await fs.mkdir(metaDir, { recursive: true });
@@ -524,54 +657,306 @@ async function syncSingleWiki(ctx, wiki, mountPath) {
524
657
  const baseUrl = (ctx.baseUrl ?? process.env.PRLL_WIKI_URL ?? process.env.PRLL_API_URL ?? '').replace(/\/+$/, '');
525
658
  const manifestUrl = `${baseUrl}/wiki/v1/orgs/${ctx.orgId}/wikis/${wiki.id}/manifest`;
526
659
  const bulkDownloadUrl = `${baseUrl}/wiki/v1/orgs/${ctx.orgId}/wikis/${wiki.id}/files/bulk-download`;
527
- // 1. Fetch server manifest
660
+ // 1. Fetch server manifest + read local manifest.
528
661
  const manifestResp = await wikiServiceFetch(manifestUrl, token);
529
662
  const serverEntries = manifestResp.data;
530
- // 2. Read local manifest
531
- const localManifest = await readLocalManifest(mountPath);
532
- // 3. Compare: find new, modified, deleted files
533
663
  const serverByPath = new Map(serverEntries.map((e) => [e.path, e]));
534
- const toDownload = [];
535
- const toDelete = [];
536
- for (const entry of serverEntries) {
537
- const local = localManifest.files[entry.path];
538
- if (!local || local.sha !== entry.sha) {
539
- toDownload.push(entry.path);
664
+ const localManifest = await readLocalManifest(mountPath);
665
+ // 2. Build the union of paths from server + local manifest + working tree.
666
+ const allPaths = new Set();
667
+ for (const e of serverEntries)
668
+ allPaths.add(e.path);
669
+ for (const p of Object.keys(localManifest.files))
670
+ allPaths.add(p);
671
+ await walkLocalFiles(mountPath, mountPath, async (_full, rel) => {
672
+ allPaths.add(rel);
673
+ });
674
+ // 3. Snapshot working-tree shas so the planner stays pure. Paths whose local
675
+ // shape is incompatible with a regular-file read (e.g. local has a directory
676
+ // where remote treats this as a file, or a parent path is a regular file
677
+ // while remote nests under it) are routed to a 'shape-conflict' failure
678
+ // instead of crashing the whole sync — they need human/agent intervention.
679
+ const localShaCache = new Map();
680
+ const shapeConflictByPath = new Map();
681
+ for (const p of allPaths) {
682
+ const probe = await probeLocalGitBlob(mountPath, p);
683
+ if (probe.kind === 'shape-conflict') {
684
+ shapeConflictByPath.set(p, probe.detail);
685
+ localShaCache.set(p, null);
540
686
  }
541
- }
542
- for (const localPath of Object.keys(localManifest.files)) {
543
- if (!serverByPath.has(localPath)) {
544
- toDelete.push(localPath);
687
+ else {
688
+ localShaCache.set(p, probe.sha);
545
689
  }
546
690
  }
547
- // 4. Bulk download changed files
548
- if (toDownload.length > 0) {
549
- const downloaded = await bulkDownloadFiles(bulkDownloadUrl, token, toDownload);
691
+ const actions = planSyncActions({
692
+ paths: allPaths,
693
+ baseSha: (p) => localManifest.files[p]?.sha ?? null,
694
+ localSha: (p) => localShaCache.get(p) ?? null,
695
+ remote: (p) => {
696
+ const r = serverByPath.get(p);
697
+ return r ? { sha: r.sha, size: r.size } : null;
698
+ },
699
+ });
700
+ // 4. Fetch all bytes we need (apply-write + conflict-write) in one bulk call.
701
+ const fetchPaths = actions
702
+ .filter((a) => a.kind === 'apply-write' || a.kind === 'conflict-write')
703
+ .map((a) => a.path);
704
+ const contentByPath = new Map();
705
+ const downloadErrorByPath = new Map();
706
+ if (fetchPaths.length > 0) {
707
+ const downloaded = await bulkDownloadFiles(bulkDownloadUrl, token, fetchPaths);
708
+ const returnedPaths = new Set();
550
709
  for (const entry of downloaded) {
551
- const fullPath = resolvePathInsideRoot(mountPath, entry.path);
552
- await fs.mkdir(path.dirname(fullPath), { recursive: true });
553
- await fs.writeFile(fullPath, Buffer.from(entry.content_base64, 'base64'));
710
+ returnedPaths.add(entry.path);
711
+ if (entry.error) {
712
+ downloadErrorByPath.set(entry.path, entry.error);
713
+ continue;
714
+ }
715
+ contentByPath.set(entry.path, Buffer.from(entry.content_base64, 'base64'));
716
+ }
717
+ // Anything we asked for but the server omitted entirely is also a failure.
718
+ for (const p of fetchPaths) {
719
+ if (!returnedPaths.has(p)) {
720
+ downloadErrorByPath.set(p, 'missing from bulk-download response');
721
+ }
554
722
  }
555
723
  }
556
- // 5. Delete removed files
557
- for (const deletePath of toDelete) {
558
- const fullPath = resolvePathInsideRoot(mountPath, deletePath);
559
- await fs.rm(fullPath, { force: true });
560
- await removeEmptyParents(mountPath, path.dirname(fullPath));
724
+ // 5. Apply actions and start with the previous manifest, mutating per action.
725
+ const newFiles = { ...localManifest.files };
726
+ const conflicts = [];
727
+ const failed = [];
728
+ let applied = 0;
729
+ let fastForwarded = 0;
730
+ // Surface shape conflicts as sync failures up front so we don't try to write
731
+ // through them. Manifest stays pinned (the path's manifest entry is left as-is).
732
+ for (const [p, detail] of shapeConflictByPath) {
733
+ failed.push({ path: p, reason: 'shape-conflict', detail });
734
+ }
735
+ for (const action of actions) {
736
+ if (shapeConflictByPath.has(action.path)) {
737
+ // Already reported. Don't attempt apply / write — would crash on EISDIR/ENOTDIR.
738
+ continue;
739
+ }
740
+ switch (action.kind) {
741
+ case 'noop':
742
+ // Preserve manifest entry unchanged.
743
+ break;
744
+ case 'apply-write': {
745
+ const buf = contentByPath.get(action.path);
746
+ if (!buf) {
747
+ failed.push({
748
+ path: action.path,
749
+ reason: 'download',
750
+ detail: downloadErrorByPath.get(action.path),
751
+ });
752
+ break;
753
+ }
754
+ // TOCTOU re-check: the snapshot was taken before the bulk-download
755
+ // network round-trip. If the user/agent edited (or deleted) this path
756
+ // in that window, demote to a conflict so we don't silently overwrite.
757
+ const snapshotSha = localShaCache.get(action.path) ?? null;
758
+ const reprobe = await probeLocalGitBlob(mountPath, action.path);
759
+ if (reprobe.kind === 'shape-conflict') {
760
+ failed.push({ path: action.path, reason: 'shape-conflict', detail: reprobe.detail });
761
+ break;
762
+ }
763
+ const currentSha = reprobe.sha;
764
+ if (currentSha === action.remoteSha) {
765
+ // Local converged on remote during the window — manifest-only fast-forward.
766
+ newFiles[action.path] = { sha: action.remoteSha, size: action.remoteSize };
767
+ fastForwarded++;
768
+ break;
769
+ }
770
+ if (currentSha !== snapshotSha) {
771
+ // Local diverged after snapshot. Surface as a conflict instead of overwriting.
772
+ const remoteCopy = await writeConflictMarker(mountPath, action.path, REMOTE_CONFLICT_SUFFIX, buf);
773
+ let reason;
774
+ // Defense-in-depth: planSyncActions only emits apply-write when
775
+ // localSha === baseSha (both non-null), so snapshotSha should never
776
+ // be null here. The branch is kept to safely classify any race that
777
+ // creates a file between snapshot and apply.
778
+ if (snapshotSha === null && currentSha !== null) {
779
+ reason = 'remote-added-local-collides';
780
+ }
781
+ else if (currentSha === null) {
782
+ reason = 'remote-changed-local-deleted';
783
+ }
784
+ else {
785
+ reason = 'remote-changed-local-edited';
786
+ }
787
+ conflicts.push({ path: action.path, remoteCopyPath: remoteCopy, reason });
788
+ break;
789
+ }
790
+ const fullPath = resolvePathInsideRoot(mountPath, action.path);
791
+ await fs.mkdir(path.dirname(fullPath), { recursive: true });
792
+ await fs.writeFile(fullPath, buf);
793
+ newFiles[action.path] = { sha: action.remoteSha, size: action.remoteSize };
794
+ applied++;
795
+ break;
796
+ }
797
+ case 'apply-delete': {
798
+ // TOCTOU re-check before deleting. The action only fires when the
799
+ // snapshot saw clean local matching the (now-deleted) base; if the
800
+ // user re-created or edited the file after the snapshot, demote to
801
+ // conflict-delete instead of dropping their work.
802
+ const snapshotSha = localShaCache.get(action.path) ?? null;
803
+ const reprobe = await probeLocalGitBlob(mountPath, action.path);
804
+ if (reprobe.kind === 'shape-conflict') {
805
+ failed.push({ path: action.path, reason: 'shape-conflict', detail: reprobe.detail });
806
+ break;
807
+ }
808
+ const currentSha = reprobe.sha;
809
+ if (currentSha === null) {
810
+ // Convergence: user also deleted the file during the TOCTOU window.
811
+ // Both sides agree — fast-forward the manifest delete.
812
+ delete newFiles[action.path];
813
+ fastForwarded++;
814
+ break;
815
+ }
816
+ if (currentSha !== snapshotSha) {
817
+ await writeConflictMarker(mountPath, action.path, REMOTE_DELETED_MARKER_SUFFIX, Buffer.alloc(0));
818
+ conflicts.push({
819
+ path: action.path,
820
+ remoteCopyPath: null,
821
+ reason: 'remote-deleted-local-edited',
822
+ });
823
+ break;
824
+ }
825
+ const fullPath = resolvePathInsideRoot(mountPath, action.path);
826
+ await fs.rm(fullPath, { force: true });
827
+ await removeEmptyParents(mountPath, path.dirname(fullPath));
828
+ delete newFiles[action.path];
829
+ applied++;
830
+ break;
831
+ }
832
+ case 'fast-forward':
833
+ // Local already matches remote — only sync the manifest pointer.
834
+ newFiles[action.path] = { sha: action.remoteSha, size: action.remoteSize };
835
+ fastForwarded++;
836
+ break;
837
+ case 'fast-forward-delete':
838
+ delete newFiles[action.path];
839
+ fastForwarded++;
840
+ break;
841
+ case 'conflict-write': {
842
+ const buf = contentByPath.get(action.path);
843
+ if (!buf) {
844
+ failed.push({
845
+ path: action.path,
846
+ reason: 'download',
847
+ detail: downloadErrorByPath.get(action.path),
848
+ });
849
+ break;
850
+ }
851
+ const remoteCopy = await writeConflictMarker(mountPath, action.path, REMOTE_CONFLICT_SUFFIX, buf);
852
+ // Reason classification:
853
+ // - base null + local present → both sides added the same path with different bytes
854
+ // - base present + local null → user deleted locally, server changed it
855
+ // - base present + local present (and ≠ both) → genuine concurrent edit
856
+ let reason;
857
+ if (action.baseSha === null) {
858
+ reason = 'remote-added-local-collides';
859
+ }
860
+ else if (action.localSha === null) {
861
+ reason = 'remote-changed-local-deleted';
862
+ }
863
+ else {
864
+ reason = 'remote-changed-local-edited';
865
+ }
866
+ conflicts.push({ path: action.path, remoteCopyPath: remoteCopy, reason });
867
+ // Manifest entry stays at its pre-sync value so the next sync still
868
+ // recognises the local edit and re-tries the merge.
869
+ break;
870
+ }
871
+ case 'conflict-delete': {
872
+ // Server deleted a file the user has local edits for. Drop a marker
873
+ // so the agent surfaces the divergence without overwriting local.
874
+ await writeConflictMarker(mountPath, action.path, REMOTE_DELETED_MARKER_SUFFIX, Buffer.alloc(0));
875
+ conflicts.push({
876
+ path: action.path,
877
+ remoteCopyPath: null,
878
+ reason: 'remote-deleted-local-edited',
879
+ });
880
+ break;
881
+ }
882
+ }
561
883
  }
562
- // 6. Save updated local manifest
884
+ // 6. Persist new manifest.
563
885
  const newManifest = {
564
886
  wiki_id: wiki.id,
565
887
  synced_at: new Date().toISOString(),
566
- files: {},
888
+ files: newFiles,
567
889
  };
568
- for (const entry of serverEntries) {
569
- newManifest.files[entry.path] = {
570
- sha: entry.sha,
571
- size: entry.size,
572
- };
573
- }
574
890
  await writeLocalManifest(mountPath, newManifest);
891
+ if (conflicts.length > 0) {
892
+ for (const c of conflicts) {
893
+ const where = c.remoteCopyPath ? ` → ${path.relative(mountPath, c.remoteCopyPath)}` : '';
894
+ console.warn(`[wiki sync] conflict on ${c.path} (${c.reason})${where}`);
895
+ }
896
+ }
897
+ if (failed.length > 0) {
898
+ for (const f of failed) {
899
+ const detail = f.detail ? ` — ${f.detail}` : '';
900
+ console.warn(`[wiki sync] failed to ${f.reason} ${f.path}${detail} — manifest unchanged, will retry next sync`);
901
+ }
902
+ }
903
+ return { applied, fastForwarded, conflicts, failed };
904
+ }
905
+ /** Compute the git blob SHA of a working-tree file, or null if absent. */
906
+ async function readLocalGitBlobSha(mountRoot, relPath) {
907
+ const probe = await probeLocalGitBlob(mountRoot, relPath);
908
+ if (probe.kind === 'shape-conflict') {
909
+ // Surface as throw at this entry point — the caller (computeLocalChanges /
910
+ // computeLocalDiff) doesn't currently model shape conflicts. syncSingleWiki
911
+ // uses probeLocalGitBlob directly so it can route the failure structurally.
912
+ throw new Error(`shape conflict at ${relPath}: ${probe.detail}`);
913
+ }
914
+ return probe.sha;
915
+ }
916
+ /**
917
+ * Read a working-tree file as a git blob, returning a structured shape so
918
+ * callers can distinguish "absent" from "shape mismatch" (directory where a
919
+ * file is expected, or a parent path is a file blocking nested reads).
920
+ */
921
+ async function probeLocalGitBlob(mountRoot, relPath) {
922
+ let fullPath;
923
+ try {
924
+ fullPath = resolvePathInsideRoot(mountRoot, relPath);
925
+ }
926
+ catch {
927
+ return { kind: 'absent', sha: null };
928
+ }
929
+ try {
930
+ const buf = await fs.readFile(fullPath);
931
+ return { kind: 'file', sha: computeGitBlobSHA(buf) };
932
+ }
933
+ catch (err) {
934
+ const code = err.code;
935
+ if (code === 'ENOENT')
936
+ return { kind: 'absent', sha: null };
937
+ if (code === 'EISDIR' || code === 'ENOTDIR') {
938
+ return { kind: 'shape-conflict', detail: code };
939
+ }
940
+ throw err;
941
+ }
942
+ }
943
+ /**
944
+ * Write a sync conflict marker under .parall-wiki/conflicts/, mirroring the
945
+ * source path so the agent (or user) can locate the upstream copy next to the
946
+ * local edit. Returns the absolute path written.
947
+ */
948
+ export async function writeConflictMarker(mountRoot, sourcePath, suffix, content) {
949
+ // Validate sourcePath under the mount root before we use it as a key under
950
+ // .parall-wiki/conflicts. The planner only feeds us paths the server returned
951
+ // (so traversal segments are not expected today), but the rest of the sync
952
+ // pipeline always goes through resolvePathInsideRoot and we want this branch
953
+ // to fail closed too — defense in depth, not exploitation today.
954
+ resolvePathInsideRoot(mountRoot, sourcePath);
955
+ const conflictsRoot = path.join(mountRoot, PRLL_WIKI_DIR, CONFLICTS_DIR);
956
+ const target = resolvePathInsideRoot(conflictsRoot, sourcePath + suffix);
957
+ await fs.mkdir(path.dirname(target), { recursive: true });
958
+ await fs.writeFile(target, content);
959
+ return target;
575
960
  }
576
961
  // ---------------------------------------------------------------------------
577
962
  // Local manifest I/O
@@ -647,7 +1032,7 @@ async function bulkDownloadFiles(bulkDownloadUrl, token, paths) {
647
1032
  // ---------------------------------------------------------------------------
648
1033
  // Local change detection (manifest-based)
649
1034
  // ---------------------------------------------------------------------------
650
- function computeGitBlobSHA(content) {
1035
+ export function computeGitBlobSHA(content) {
651
1036
  // Manifest SHAs come from Gitea's Git blob object IDs, not raw content hashes.
652
1037
  // This matches Git's current SHA-1 blob format; if the server ever switches to
653
1038
  // SHA-256 object IDs, this helper must change in lockstep.
@@ -1741,6 +2126,38 @@ function normalizeOptionalString(value) {
1741
2126
  const normalized = value?.trim();
1742
2127
  return normalized ? normalized : undefined;
1743
2128
  }
2129
+ async function resolveRuntimeTaskStep(ctx, runtime) {
2130
+ const sessionId = normalizeOptionalString(runtime.sessionId);
2131
+ const stepId = normalizeOptionalString(runtime.stepId);
2132
+ const agentId = resolveAgentId(ctx.agentId);
2133
+ if (!sessionId || !stepId || !agentId) {
2134
+ return null;
2135
+ }
2136
+ try {
2137
+ const step = await ctx.client.getAgentSessionStep(ctx.orgId, agentId, sessionId, stepId);
2138
+ if (step.target_type !== 'task' || !step.target_id) {
2139
+ return null;
2140
+ }
2141
+ return step;
2142
+ }
2143
+ catch {
2144
+ return null;
2145
+ }
2146
+ }
2147
+ function buildChangesetTaskComment(changeset, mode) {
2148
+ const verb = mode === 'merged' ? 'Merged wiki changeset' : 'Proposed wiki changeset';
2149
+ const normalizedTitle = changeset.title.replace(/\s+/g, ' ').trim();
2150
+ return `${verb}: [changeset ${changeset.id}](${COMMENT_TARGET.changeset(changeset.id, changeset.wiki_id)}) - ${normalizedTitle}`;
2151
+ }
2152
+ function formatBackfillError(err) {
2153
+ if (err instanceof ApiError) {
2154
+ return `${err.code ?? err.status} ${err.message}`;
2155
+ }
2156
+ if (err instanceof Error) {
2157
+ return err.message;
2158
+ }
2159
+ return String(err);
2160
+ }
1744
2161
  async function pathExists(target) {
1745
2162
  try {
1746
2163
  await fs.access(target);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parall/cli",
3
- "version": "1.19.0",
3
+ "version": "1.20.1",
4
4
  "description": "CLI client for Parall — universal agent & human access to Parall API",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -30,7 +30,7 @@
30
30
  "@modelcontextprotocol/sdk": "1.27.1",
31
31
  "commander": "^13.0.0",
32
32
  "zod": "^4.3.6",
33
- "@parall/sdk": "1.19.0"
33
+ "@parall/sdk": "1.20.1"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/node": "^22.0.0",
@@ -38,6 +38,7 @@
38
38
  },
39
39
  "scripts": {
40
40
  "build": "tsc",
41
- "dev": "tsc --watch"
41
+ "dev": "tsc --watch",
42
+ "test": "pnpm run build && node --test test/*.test.mjs"
42
43
  }
43
44
  }