@gkorepanov/ccodex 0.4.5 → 0.4.7
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/README.md +31 -3
- package/config/compatibility.json +1 -1
- package/dist/cli/args.d.ts +3 -0
- package/dist/cli/args.js +4 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/delegate.js +1 -0
- package/dist/cli/delegate.js.map +1 -1
- package/dist/cli/main.js +21 -22
- package/dist/cli/main.js.map +1 -1
- package/dist/daemon/daemon.js +5 -4
- package/dist/daemon/daemon.js.map +1 -1
- package/dist/daemon/supervisor.d.ts +1 -0
- package/dist/daemon/supervisor.js +13 -3
- package/dist/daemon/supervisor.js.map +1 -1
- package/dist/desktop/launchAgent.d.ts +26 -0
- package/dist/desktop/launchAgent.js +122 -0
- package/dist/desktop/launchAgent.js.map +1 -0
- package/dist/desktop/stdioFrontend.d.ts +9 -0
- package/dist/desktop/stdioFrontend.js +305 -0
- package/dist/desktop/stdioFrontend.js.map +1 -0
- package/dist/gateway/clientConnection.js +2 -7
- package/dist/gateway/clientConnection.js.map +1 -1
- package/dist/handoff/service.d.ts +2 -2
- package/dist/handoff/service.js +158 -67
- package/dist/handoff/service.js.map +1 -1
- package/dist/handoff/store.d.ts +7 -4
- package/dist/handoff/store.js +55 -19
- package/dist/handoff/store.js.map +1 -1
- package/dist/management/doctor.js +31 -2
- package/dist/management/doctor.js.map +1 -1
- package/dist/management/lifecycle.js +80 -26
- package/dist/management/lifecycle.js.map +1 -1
- package/dist/management/setup.d.ts +3 -0
- package/dist/management/setup.js +75 -94
- package/dist/management/setup.js.map +1 -1
- package/dist/management/shellRouting.d.ts +14 -0
- package/dist/management/shellRouting.js +64 -0
- package/dist/management/shellRouting.js.map +1 -0
- package/dist/management/shims.d.ts +16 -3
- package/dist/management/shims.js +67 -12
- package/dist/management/shims.js.map +1 -1
- package/examples/config.toml +2 -0
- package/package.json +4 -4
package/dist/handoff/service.js
CHANGED
|
@@ -287,6 +287,13 @@ export class CrossProviderForks {
|
|
|
287
287
|
await this.deleteLogicalThread(publicThreadId, clientStock);
|
|
288
288
|
return { provider: resolved.epoch.provider, result: {} };
|
|
289
289
|
}
|
|
290
|
+
if (method === "thread/archive") {
|
|
291
|
+
this.store.cancelProviderSwitches(publicThreadId, "Task archived.");
|
|
292
|
+
if (resolved.epoch.backendThreadId.startsWith("ccodex-provisional:")) {
|
|
293
|
+
await this.deleteLogicalThread(publicThreadId, clientStock);
|
|
294
|
+
return { provider: resolved.epoch.provider, result: {} };
|
|
295
|
+
}
|
|
296
|
+
}
|
|
290
297
|
if (resolved.epoch.backendThreadId.startsWith("ccodex-provisional:")) {
|
|
291
298
|
return this.requestProvisionalLogical(method, publicParams, resolved);
|
|
292
299
|
}
|
|
@@ -523,9 +530,12 @@ export class CrossProviderForks {
|
|
|
523
530
|
this.patchLogicalThread(resolved.logical.publicThreadId, { name: params.name });
|
|
524
531
|
return { provider: resolved.epoch.provider, result: {} };
|
|
525
532
|
}
|
|
533
|
+
if (method === "thread/goal/get") {
|
|
534
|
+
return { provider: resolved.epoch.provider, result: { goal: null } };
|
|
535
|
+
}
|
|
526
536
|
throw invalidParams("This fork is waiting for Codex App to select a completed history boundary.");
|
|
527
537
|
}
|
|
528
|
-
async forkLogical(params, clientStock) {
|
|
538
|
+
async forkLogical(params, clientStock, connectionId) {
|
|
529
539
|
const source = this.epochs.resolve(params.threadId);
|
|
530
540
|
if (!source)
|
|
531
541
|
throw invalidParams(`Unknown logical thread '${params.threadId}'.`);
|
|
@@ -534,11 +544,54 @@ export class CrossProviderForks {
|
|
|
534
544
|
}
|
|
535
545
|
const currentTurns = await this.currentBackendTurns(source, this.daemonStock ?? clientStock);
|
|
536
546
|
const visible = this.epochs.snapshotTurns(params.threadId, currentTurns);
|
|
547
|
+
const boundaryIndex = visible.findLastIndex((turn) => turn.turn.status === "completed" && turn.epochId && turn.providerTurnId);
|
|
548
|
+
const boundary = visible[boundaryIndex];
|
|
549
|
+
if (!boundary?.epochId || !boundary.providerTurnId) {
|
|
550
|
+
throw invalidParams("Cannot fork before the task has a completed provider turn.");
|
|
551
|
+
}
|
|
552
|
+
const selectedEpoch = this.store.getEpoch(boundary.epochId);
|
|
553
|
+
if (!selectedEpoch || !this.store.epochBelongsToLineage(params.threadId, selectedEpoch.id)) {
|
|
554
|
+
throw invalidParams("Fork boundary is outside the task's provider lineage.");
|
|
555
|
+
}
|
|
537
556
|
const targetId = uuidv7();
|
|
538
|
-
|
|
557
|
+
let forked;
|
|
558
|
+
let stockBuildOwner;
|
|
559
|
+
if (selectedEpoch.provider === "claude") {
|
|
560
|
+
forked = await this.claude.forkThread({
|
|
561
|
+
threadId: selectedEpoch.backendThreadId,
|
|
562
|
+
lastTurnId: boundary.providerTurnId,
|
|
563
|
+
model: selectedEpoch.model,
|
|
564
|
+
}, params.threadId);
|
|
565
|
+
}
|
|
566
|
+
else {
|
|
567
|
+
stockBuildOwner = connectionId;
|
|
568
|
+
if (stockBuildOwner) {
|
|
569
|
+
if (this.stockTargetBuilds.has(stockBuildOwner)) {
|
|
570
|
+
throw new Error("Another stock thread materialization is already active on this connection.");
|
|
571
|
+
}
|
|
572
|
+
this.stockTargetBuilds.set(stockBuildOwner, {
|
|
573
|
+
awaitingStart: true,
|
|
574
|
+
threadIds: new Set(),
|
|
575
|
+
messages: [],
|
|
576
|
+
expectedForkedFromId: selectedEpoch.backendThreadId,
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
try {
|
|
580
|
+
forked = await clientStock.request("thread/fork", {
|
|
581
|
+
threadId: selectedEpoch.backendThreadId,
|
|
582
|
+
lastTurnId: boundary.providerTurnId,
|
|
583
|
+
model: selectedEpoch.model,
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
catch (error) {
|
|
587
|
+
if (stockBuildOwner)
|
|
588
|
+
this.stockTargetBuilds.delete(stockBuildOwner);
|
|
589
|
+
throw error;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
539
592
|
const now = Math.floor(Date.now() / 1_000);
|
|
540
593
|
const thread = {
|
|
541
|
-
...
|
|
594
|
+
...forked.thread,
|
|
542
595
|
id: targetId,
|
|
543
596
|
sessionId: source.logical.thread.sessionId,
|
|
544
597
|
forkedFromId: params.threadId,
|
|
@@ -548,67 +601,102 @@ export class CrossProviderForks {
|
|
|
548
601
|
recencyAt: now,
|
|
549
602
|
status: { type: "notLoaded" },
|
|
550
603
|
name: source.logical.thread.name ? `${source.logical.thread.name} (fork)` : null,
|
|
551
|
-
turns:
|
|
552
|
-
};
|
|
553
|
-
const target = this.store.createLogicalThread({
|
|
554
|
-
thread,
|
|
555
|
-
epoch: {
|
|
556
|
-
id: uuidv7(),
|
|
557
|
-
provider: source.epoch.provider,
|
|
558
|
-
backendThreadId: placeholderId,
|
|
559
|
-
model: source.epoch.model,
|
|
560
|
-
settings: source.epoch.settings,
|
|
561
|
-
},
|
|
562
|
-
});
|
|
563
|
-
if (visible.length > 0 && !this.store.replaceLogicalTurns(targetId, target.revision, visible)) {
|
|
564
|
-
throw new Error("Failed to persist provisional logical fork history.");
|
|
565
|
-
}
|
|
566
|
-
const provisional = this.epochs.resolve(targetId);
|
|
567
|
-
if (!this.store.createForkSelection({
|
|
568
|
-
targetPublicThreadId: targetId,
|
|
569
|
-
sourcePublicThreadId: params.threadId,
|
|
570
|
-
provisionalEpochId: provisional.epoch.id,
|
|
571
|
-
}))
|
|
572
|
-
throw new Error("Failed to persist logical fork selection.");
|
|
573
|
-
const settings = source.epoch.settings;
|
|
574
|
-
const response = {
|
|
575
|
-
thread: { ...thread, turns: params.excludeTurns ? [] : thread.turns },
|
|
576
|
-
model: source.epoch.model,
|
|
577
|
-
modelProvider: source.epoch.provider === "claude" ? "claude" : "openai",
|
|
578
|
-
serviceTier: typeof settings.serviceTier === "string" ? settings.serviceTier : null,
|
|
579
|
-
cwd: thread.cwd,
|
|
580
|
-
runtimeWorkspaceRoots: Array.isArray(settings.runtimeWorkspaceRoots)
|
|
581
|
-
? settings.runtimeWorkspaceRoots : [],
|
|
582
|
-
instructionSources: Array.isArray(settings.instructionSources)
|
|
583
|
-
? settings.instructionSources : [],
|
|
584
|
-
approvalPolicy: (settings.approvalPolicy ?? "on-request"),
|
|
585
|
-
approvalsReviewer: (settings.approvalsReviewer ?? "user"),
|
|
586
|
-
sandbox: (settings.sandbox ?? settings.sandboxPolicy ?? { type: "readOnly" }),
|
|
587
|
-
activePermissionProfile: (settings.activePermissionProfile ?? null),
|
|
588
|
-
reasoningEffort: (settings.reasoningEffort ?? settings.effort ?? null),
|
|
589
|
-
multiAgentMode: "explicitRequestOnly",
|
|
604
|
+
turns: [],
|
|
590
605
|
};
|
|
591
|
-
|
|
592
|
-
|
|
606
|
+
try {
|
|
607
|
+
const target = this.store.createLogicalThread({
|
|
608
|
+
thread,
|
|
609
|
+
epoch: {
|
|
610
|
+
id: uuidv7(),
|
|
611
|
+
provider: selectedEpoch.provider,
|
|
612
|
+
backendThreadId: forked.thread.id,
|
|
613
|
+
model: selectedEpoch.model,
|
|
614
|
+
settings: selectedEpoch.settings,
|
|
615
|
+
},
|
|
616
|
+
});
|
|
617
|
+
const inherited = visible.slice(0, boundaryIndex + 1).filter((turn) => turn.epochId !== selectedEpoch.id);
|
|
618
|
+
if (inherited.length > 0 && !this.store.replaceLogicalTurns(targetId, target.revision, inherited)) {
|
|
619
|
+
throw new Error("Failed to persist logical fork history.");
|
|
620
|
+
}
|
|
621
|
+
this.subscriptions?.aliasThread(forked.thread.id, targetId);
|
|
622
|
+
if (stockBuildOwner)
|
|
623
|
+
this.stockTargetBuilds.delete(stockBuildOwner);
|
|
624
|
+
if (selectedEpoch.provider === "claude") {
|
|
625
|
+
await this.claude.announceThread(forked.thread);
|
|
626
|
+
}
|
|
627
|
+
else {
|
|
628
|
+
this.subscriptions?.emitPublic(targetId, "thread/started", {
|
|
629
|
+
thread: this.epochs.projectThread(targetId, forked.thread, false),
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
const settings = selectedEpoch.settings;
|
|
633
|
+
return {
|
|
634
|
+
...forked,
|
|
635
|
+
thread: this.epochs.projectThread(targetId, forked.thread, !params.excludeTurns),
|
|
636
|
+
model: selectedEpoch.model,
|
|
637
|
+
modelProvider: selectedEpoch.provider === "claude" ? "claude" : "openai",
|
|
638
|
+
serviceTier: typeof settings.serviceTier === "string" ? settings.serviceTier : null,
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
catch (error) {
|
|
642
|
+
if (stockBuildOwner)
|
|
643
|
+
this.stockTargetBuilds.delete(stockBuildOwner);
|
|
644
|
+
this.store.deleteLogicalThread(targetId);
|
|
645
|
+
if (!forked?.thread.id)
|
|
646
|
+
throw error;
|
|
647
|
+
if (selectedEpoch.provider === "claude") {
|
|
648
|
+
await this.claude.deleteThread(forked.thread.id).catch(() => undefined);
|
|
649
|
+
}
|
|
650
|
+
else {
|
|
651
|
+
await clientStock.request("thread/delete", { threadId: forked.thread.id }).catch(() => undefined);
|
|
652
|
+
}
|
|
653
|
+
throw error;
|
|
654
|
+
}
|
|
593
655
|
}
|
|
594
|
-
async
|
|
656
|
+
async rollbackLogicalThread(params, clientStock, connectionId) {
|
|
595
657
|
const selection = this.store.getForkSelection(params.threadId);
|
|
596
658
|
const target = this.epochs.resolve(params.threadId);
|
|
597
|
-
if (!
|
|
598
|
-
throw invalidParams(`
|
|
659
|
+
if (!target)
|
|
660
|
+
throw invalidParams(`Unknown logical thread '${params.threadId}'.`);
|
|
661
|
+
if (!Number.isInteger(params.numTurns) || params.numTurns < 0) {
|
|
662
|
+
throw invalidParams("numTurns must be a non-negative integer.");
|
|
599
663
|
}
|
|
600
|
-
const
|
|
664
|
+
const pendingFork = selection?.status === "pending" ? selection : undefined;
|
|
665
|
+
const currentTurns = await this.currentBackendTurns(target, this.daemonStock ?? clientStock);
|
|
666
|
+
const turns = this.epochs.snapshotTurns(params.threadId, currentTurns);
|
|
601
667
|
const keep = turns.length - params.numTurns;
|
|
602
668
|
if (keep <= 0)
|
|
603
|
-
throw invalidParams("
|
|
669
|
+
throw invalidParams("Rollback removed every provider turn.");
|
|
604
670
|
const retained = turns.slice(0, keep);
|
|
605
671
|
const boundary = [...retained].reverse().find((turn) => turn.epochId && turn.providerTurnId);
|
|
606
672
|
if (!boundary?.epochId || !boundary.providerTurnId) {
|
|
607
|
-
throw invalidParams("
|
|
673
|
+
throw invalidParams("Rollback has no provider-backed turn boundary.");
|
|
608
674
|
}
|
|
609
675
|
const selectedEpoch = this.store.getEpoch(boundary.epochId);
|
|
610
|
-
|
|
611
|
-
|
|
676
|
+
const selectedLineage = pendingFork?.sourcePublicThreadId ?? params.threadId;
|
|
677
|
+
if (!selectedEpoch || !this.store.epochBelongsToLineage(selectedLineage, selectedEpoch.id)) {
|
|
678
|
+
throw invalidParams("Rollback points outside the thread's provider lineage.");
|
|
679
|
+
}
|
|
680
|
+
if (!pendingFork && selectedEpoch.id === target.epoch.id) {
|
|
681
|
+
if (params.numTurns === 0) {
|
|
682
|
+
const backend = target.epoch.provider === "claude"
|
|
683
|
+
? this.claude.readThread(target.epoch.backendThreadId, true).thread
|
|
684
|
+
: (await clientStock.request("thread/read", {
|
|
685
|
+
threadId: target.epoch.backendThreadId,
|
|
686
|
+
includeTurns: true,
|
|
687
|
+
})).thread;
|
|
688
|
+
return { thread: this.epochs.projectThread(params.threadId, backend, true) };
|
|
689
|
+
}
|
|
690
|
+
const rolled = target.epoch.provider === "claude"
|
|
691
|
+
? await this.claude.rollbackThread({
|
|
692
|
+
threadId: target.epoch.backendThreadId,
|
|
693
|
+
numTurns: params.numTurns,
|
|
694
|
+
})
|
|
695
|
+
: await clientStock.request("thread/rollback", {
|
|
696
|
+
threadId: target.epoch.backendThreadId,
|
|
697
|
+
numTurns: params.numTurns,
|
|
698
|
+
});
|
|
699
|
+
return { thread: this.epochs.projectThread(params.threadId, rolled.thread, true) };
|
|
612
700
|
}
|
|
613
701
|
let forked;
|
|
614
702
|
let stockBuildOwner;
|
|
@@ -649,16 +737,17 @@ export class CrossProviderForks {
|
|
|
649
737
|
...forked.thread,
|
|
650
738
|
id: params.threadId,
|
|
651
739
|
sessionId: target.logical.thread.sessionId,
|
|
652
|
-
forkedFromId:
|
|
653
|
-
parentThreadId:
|
|
740
|
+
forkedFromId: target.logical.thread.forkedFromId,
|
|
741
|
+
parentThreadId: target.logical.thread.parentThreadId,
|
|
654
742
|
createdAt: target.logical.thread.createdAt,
|
|
655
743
|
name: target.logical.thread.name,
|
|
656
744
|
turns: [],
|
|
657
745
|
};
|
|
658
|
-
const committed = this.store.
|
|
746
|
+
const committed = this.store.commitLogicalRollback({
|
|
659
747
|
targetPublicThreadId: params.threadId,
|
|
660
|
-
|
|
661
|
-
|
|
748
|
+
expectedCurrentEpochId: target.epoch.id,
|
|
749
|
+
expectedThreadRevision: target.logical.revision,
|
|
750
|
+
selectedEpochId: selectedEpoch.id,
|
|
662
751
|
targetEpoch: {
|
|
663
752
|
id: uuidv7(),
|
|
664
753
|
provider: selectedEpoch.provider,
|
|
@@ -678,18 +767,20 @@ export class CrossProviderForks {
|
|
|
678
767
|
if (!committed) {
|
|
679
768
|
if (stockBuildOwner)
|
|
680
769
|
this.stockTargetBuilds.delete(stockBuildOwner);
|
|
681
|
-
throw new Error("Logical
|
|
770
|
+
throw new Error("Logical rollback lost its atomic commit boundary.");
|
|
682
771
|
}
|
|
683
772
|
this.subscriptions?.suppress(target.epoch.backendThreadId);
|
|
684
773
|
this.subscriptions?.aliasThread(forked.thread.id, params.threadId);
|
|
685
774
|
if (stockBuildOwner)
|
|
686
775
|
this.stockTargetBuilds.delete(stockBuildOwner);
|
|
687
|
-
if (
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
776
|
+
if (pendingFork) {
|
|
777
|
+
if (selectedEpoch.provider === "claude")
|
|
778
|
+
await this.claude.announceThread(forked.thread);
|
|
779
|
+
else
|
|
780
|
+
this.subscriptions?.emitPublic(params.threadId, "thread/started", {
|
|
781
|
+
thread: this.epochs.projectThread(params.threadId, forked.thread, true),
|
|
782
|
+
});
|
|
783
|
+
}
|
|
693
784
|
this.subscriptions?.emitPublic(params.threadId, "thread/settings/updated", {
|
|
694
785
|
threadId: params.threadId,
|
|
695
786
|
threadSettings: {
|
|
@@ -1335,20 +1426,20 @@ export class CrossProviderForks {
|
|
|
1335
1426
|
}
|
|
1336
1427
|
async deleteLogicalThread(publicThreadId, clientStock) {
|
|
1337
1428
|
const stock = this.daemonStock ?? clientStock;
|
|
1429
|
+
this.store.cancelProviderSwitches(publicThreadId, "Task deleted.");
|
|
1338
1430
|
const mappings = this.store.listBackendMappings()
|
|
1339
1431
|
.filter((mapping) => mapping.publicThreadId === publicThreadId
|
|
1340
1432
|
&& !mapping.backendThreadId.startsWith("ccodex-provisional:"));
|
|
1341
1433
|
for (const mapping of mappings)
|
|
1342
1434
|
this.subscriptions?.suppress(mapping.backendThreadId);
|
|
1435
|
+
this.store.deleteLogicalThread(publicThreadId);
|
|
1436
|
+
this.subscriptions?.threadDeleted(publicThreadId);
|
|
1343
1437
|
await Promise.allSettled(mappings.map((mapping) => mapping.provider === "claude"
|
|
1344
1438
|
? this.claude.deleteThread(mapping.backendThreadId)
|
|
1345
1439
|
: stock.request("thread/delete", { threadId: mapping.backendThreadId })));
|
|
1346
1440
|
for (const mapping of mappings) {
|
|
1347
1441
|
this.subscriptions?.unaliasThread(mapping.backendThreadId);
|
|
1348
1442
|
}
|
|
1349
|
-
this.store.clearPending(publicThreadId);
|
|
1350
|
-
this.store.deleteLogicalThread(publicThreadId);
|
|
1351
|
-
this.subscriptions?.threadDeleted(publicThreadId);
|
|
1352
1443
|
}
|
|
1353
1444
|
async providerSwitchSource(job, stock, _connectionId) {
|
|
1354
1445
|
const resolved = this.epochs.resolve(job.publicThreadId);
|