@ctrl-spc/cs 0.7.13 → 0.7.15
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 +50 -3
- package/dist/autostart.js +103 -122
- package/dist/companion-ui.js +34 -6
- package/dist/companion.js +59 -150
- package/dist/config.js +52 -1
- package/dist/daemon-lifecycle.js +548 -0
- package/dist/daemon-lock.js +149 -42
- package/dist/daemon-processes.js +756 -0
- package/dist/daemon.js +14 -46
- package/dist/darwin-coalition.js +340 -0
- package/dist/index.js +70 -74
- package/dist/login.js +5 -3
- package/dist/native/darwin-coalition +0 -0
- package/dist/native/darwin-coalition.build.json +1 -0
- package/dist/native/darwin-coalition.c +145 -0
- package/dist/orchestrator.js +620 -428
- package/dist/panel3/checkout.js +100 -8
- package/dist/panel3/prompt.js +16 -8
- package/dist/panel3/run.js +891 -541
- package/dist/panel3/spawn.js +59 -11
- package/dist/panel3/tools.js +28 -5
- package/dist/presence.js +183 -24
- package/dist/supabase.js +43 -9
- package/dist/win-shell.js +464 -1
- package/dist/windows-job.js +312 -0
- package/package.json +4 -3
package/dist/panel3/checkout.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* two owners of it is how two answers are born.
|
|
15
15
|
*/
|
|
16
16
|
import { execFileSync } from 'node:child_process';
|
|
17
|
-
import { existsSync, mkdirSync, readdirSync, statSync } from 'node:fs';
|
|
17
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
18
18
|
import { join } from 'node:path';
|
|
19
19
|
import { configDir, readCodebasePaths } from '../config.js';
|
|
20
20
|
function isDirectory(path) {
|
|
@@ -413,6 +413,8 @@ export function commitCardWork(folder,
|
|
|
413
413
|
// Name the codebase and branch in failures, without exposing a local path.
|
|
414
414
|
describe) {
|
|
415
415
|
try {
|
|
416
|
+
if (gitOperationInProgress(folder))
|
|
417
|
+
throw pathFree('A Git operation is still in progress on this card. Use recover_landing to finish the approved recovery before landing.');
|
|
416
418
|
git(folder, ['add', '-A']);
|
|
417
419
|
if (git(folder, ['status', '--porcelain']).length > 0) {
|
|
418
420
|
git(folder, ['commit', '--no-verify', '-m', 'Work from this card']);
|
|
@@ -447,28 +449,118 @@ export function mergeIntoBase(source, branch, base, codebaseName) {
|
|
|
447
449
|
The worktree holding the base may be the person's own checkout, and the
|
|
448
450
|
step below would abort on failure. So a conflicted merge already sitting
|
|
449
451
|
there stops this before anything is attempted. */
|
|
450
|
-
|
|
451
|
-
|
|
452
|
+
let inProgress;
|
|
453
|
+
try {
|
|
454
|
+
inProgress = gitOperationInProgress(holder);
|
|
455
|
+
}
|
|
456
|
+
catch {
|
|
457
|
+
throw pathFree(`Could not inspect ${codebaseName}'s ${base} on this machine. Nothing was merged; check that working copy before retrying.`);
|
|
458
|
+
}
|
|
459
|
+
if (inProgress) {
|
|
460
|
+
throw pathFree(`A Git operation is already in progress in the copy of ${codebaseName} holding ${base} on this `
|
|
452
461
|
+ `machine. Nothing was done, and this card's work is still on ${branch}.`);
|
|
453
462
|
}
|
|
454
463
|
try {
|
|
455
464
|
git(holder, ['merge', '--no-edit', branch]);
|
|
456
465
|
}
|
|
457
466
|
catch {
|
|
467
|
+
const conflicts = git(holder, ['diff', '--name-only', '--diff-filter=U']).length > 0;
|
|
458
468
|
/* ITS OWN TRY, SO A FAILED ABORT CANNOT REPLACE THE COMPOSED FAILURE with
|
|
459
469
|
git's own text, which names a folder. */
|
|
470
|
+
if (gitOk(holder, ['rev-parse', '--verify', 'MERGE_HEAD'])) {
|
|
471
|
+
try {
|
|
472
|
+
git(holder, ['merge', '--abort']);
|
|
473
|
+
}
|
|
474
|
+
catch {
|
|
475
|
+
throw pathFree(`The merge in ${codebaseName} could not be rolled back. ${base} needs recovery before retrying; this card's branch is preserved.`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
throw Object.assign(pathFree(`This card's work will not go back onto ${base} in ${codebaseName} cleanly. Nothing was `
|
|
479
|
+
+ `merged, ${base} is where it was, and the work is still on ${branch}.`), { mergeConflicts: conflicts });
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
function gitOperationInProgress(folder) {
|
|
483
|
+
return ['MERGE_HEAD', 'CHERRY_PICK_HEAD', 'REVERT_HEAD', 'rebase-merge', 'rebase-apply', 'sequencer']
|
|
484
|
+
.some(name => existsSync(git(folder, ['rev-parse', '--path-format=absolute', '--git-path', name])));
|
|
485
|
+
}
|
|
486
|
+
const reconciliationMessage = (card) => `CTRL+SPC: reconcile ${card.branch} with ${card.base}`;
|
|
487
|
+
/** Git owns the durable recovery state. Its merge message identifies our merge;
|
|
488
|
+
* a user's in-progress operation is never continued or aborted by recovery. */
|
|
489
|
+
export function ownsReconciliation(card) {
|
|
490
|
+
if (!gitOk(card.folder, ['rev-parse', '--verify', 'MERGE_HEAD']))
|
|
491
|
+
return false;
|
|
492
|
+
try {
|
|
493
|
+
const message = git(card.folder, ['rev-parse', '--path-format=absolute', '--git-path', 'MERGE_MSG']);
|
|
494
|
+
return readFileSync(message, 'utf8').split('\n')[0] === reconciliationMessage(card);
|
|
495
|
+
}
|
|
496
|
+
catch {
|
|
497
|
+
throw pathFree('Could not read this card’s merge recovery state. Its working copy needs recovery on this machine.');
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
function checkCardBranch(card) {
|
|
501
|
+
if (git(card.folder, ['branch', '--show-current']) !== card.branch || card.branch === card.base) {
|
|
502
|
+
throw pathFree('The card is no longer on its assigned branch. Restore its working copy before retrying the approved merge.');
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
/** Prepare only the card's working copy. The sandboxed agent resolves ordinary
|
|
506
|
+
* files; the product keeps ownership of the index, merge state, and base branch. */
|
|
507
|
+
export function prepareCardReconciliation(card) {
|
|
508
|
+
try {
|
|
509
|
+
checkCardBranch(card);
|
|
510
|
+
if (gitOperationInProgress(card.folder)) {
|
|
511
|
+
if (!ownsReconciliation(card))
|
|
512
|
+
throw pathFree('Another Git operation is already in progress on this card. It was left untouched.');
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
if (git(card.folder, ['status', '--porcelain']).length > 0) {
|
|
516
|
+
throw pathFree('The card has uncommitted changes. Preserve and verify them before retrying the approved merge with recover_landing(action="finish").');
|
|
517
|
+
}
|
|
518
|
+
try {
|
|
519
|
+
git(card.folder, ['merge', '--no-commit', '--no-ff', '-m', reconciliationMessage(card), `refs/heads/${card.base}`]);
|
|
520
|
+
}
|
|
521
|
+
catch (error) {
|
|
522
|
+
if (!ownsReconciliation(card))
|
|
523
|
+
throw error;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
const conflicts = git(card.folder, ['diff', '--name-only', '--diff-filter=U']);
|
|
527
|
+
return { conflicts: conflicts ? conflicts.split('\n').length : 0 };
|
|
528
|
+
}
|
|
529
|
+
catch (error) {
|
|
530
|
+
if (error?.pathFree)
|
|
531
|
+
throw error;
|
|
532
|
+
throw pathFree(`Could not prepare merge recovery on ${card.branch}. The work remains on its branch; retry recovery on this machine.`);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
/** Called only after the owner reports verification under the active token. */
|
|
536
|
+
export function finishCardReconciliation(card) {
|
|
537
|
+
try {
|
|
538
|
+
checkCardBranch(card);
|
|
539
|
+
if (!gitOperationInProgress(card.folder))
|
|
540
|
+
return;
|
|
541
|
+
if (!ownsReconciliation(card))
|
|
542
|
+
throw pathFree('Another Git operation is already in progress on this card. It was left untouched.');
|
|
543
|
+
// Stage resolved edits, including deletions. Refuse remaining conflict
|
|
544
|
+
// markers before committing, even though staging clears Git's U entries.
|
|
545
|
+
git(card.folder, ['add', '-A']);
|
|
460
546
|
try {
|
|
461
|
-
git(
|
|
547
|
+
git(card.folder, ['diff', '--cached', '--check']);
|
|
548
|
+
}
|
|
549
|
+
catch {
|
|
550
|
+
throw pathFree('The resolution still contains conflict markers or whitespace errors. Fix the files, verify the result, and retry recover_landing(action="finish"). Nothing was merged onto the base.');
|
|
462
551
|
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
552
|
+
git(card.folder, ['commit', '--no-edit']);
|
|
553
|
+
}
|
|
554
|
+
catch (error) {
|
|
555
|
+
if (error?.pathFree)
|
|
556
|
+
throw error;
|
|
557
|
+
throw pathFree(`Could not finish merge recovery on ${card.branch}. The resolution is preserved; retry recovery on this machine.`);
|
|
466
558
|
}
|
|
467
559
|
}
|
|
468
560
|
export function settleCardWorktree(folder) {
|
|
469
561
|
// Completion is not permission to commit. Preserve unfinished Git work,
|
|
470
562
|
// including staged and untracked files, for the person to review or resume.
|
|
471
|
-
if (git(folder, ['status', '--porcelain']).length > 0)
|
|
563
|
+
if (gitOperationInProgress(folder) || git(folder, ['status', '--porcelain']).length > 0)
|
|
472
564
|
return false;
|
|
473
565
|
// Only ignored build/dependency files can remain in a clean copy.
|
|
474
566
|
removeWorktree(folder);
|
package/dist/panel3/prompt.js
CHANGED
|
@@ -380,6 +380,9 @@ const LANDING_LINES = {
|
|
|
380
380
|
'The product writes the question and both answers, and it performs the merge itself if they',
|
|
381
381
|
'choose to put the work back. Landing is the product\'s job and never yours: never merge and',
|
|
382
382
|
'never push.',
|
|
383
|
+
'If that approved merge needs recovery, call recover_landing(action="prepare"). Resolve and verify',
|
|
384
|
+
'the files, write_report(work_complete=true), then recover_landing(action="finish"). Approval persists;',
|
|
385
|
+
'do not ask again or request broader Git permissions. The product handles the Git updates.',
|
|
383
386
|
],
|
|
384
387
|
branch: [
|
|
385
388
|
'Finished work in this codebase stays on its branch. Nothing is pushed and nothing is merged.',
|
|
@@ -862,7 +865,18 @@ export const landingOutcomeContext = (landing) => {
|
|
|
862
865
|
...head,
|
|
863
866
|
`They chose to put this work back, and the product has merged ${landing.branch} onto `
|
|
864
867
|
+ `${landing.base} on this machine. It is done and it is not yours to do.`,
|
|
865
|
-
'
|
|
868
|
+
'Record completion with write_report(work_complete=true), then say so plainly in your reply, name the branch it went onto, and finish the card.',
|
|
869
|
+
];
|
|
870
|
+
}
|
|
871
|
+
if (landing.outcome === 'reconciling') {
|
|
872
|
+
return [
|
|
873
|
+
...head,
|
|
874
|
+
`The merge is already approved. Nothing has landed onto ${landing.base} yet.`,
|
|
875
|
+
`The product brought ${landing.base} into this card's branch ${landing.branch} for recovery; ${landing.conflicts} files are marked as conflicted.`,
|
|
876
|
+
'Resolve the files in your current working copy, preserve both the assignment and current base changes, and verify the combined result.',
|
|
877
|
+
'Use read-only Git commands to inspect it. The product owns Git metadata: do not stage, commit, merge or rebase yourself, and do not ask for wider filesystem access.',
|
|
878
|
+
'After verification, call write_report(work_complete=true), then recover_landing(action="finish"). If the base advances and new conflicts appear, resolve and verify again.',
|
|
879
|
+
'After a restart, recover_landing(action="prepare") resumes the same recovery without discarding your edits. Do not ask for the same merge approval again.',
|
|
866
880
|
];
|
|
867
881
|
}
|
|
868
882
|
if (landing.outcome === 'leave') {
|
|
@@ -883,13 +897,7 @@ export const landingOutcomeContext = (landing) => {
|
|
|
883
897
|
'offer leaves it with them.',
|
|
884
898
|
];
|
|
885
899
|
}
|
|
886
|
-
|
|
887
|
-
OBEY THAT. ═══ The card reads done the moment the owner replies, by
|
|
888
|
-
`panel3_answer`, and nothing an agent writes changes it. Telling it
|
|
889
|
-
otherwise would be a rule that can only be broken. What it can do is say
|
|
890
|
-
plainly that the work did not go back and where it still is, which is what
|
|
891
|
-
the person needs; C3 replaces this relay with a question, and a question is
|
|
892
|
-
what keeps the card open. */
|
|
900
|
+
// A non-conflict refusal still needs a truthful outcome and a concrete next action.
|
|
893
901
|
return [
|
|
894
902
|
...head,
|
|
895
903
|
'They chose to put this work back and the product could not do it:',
|