@zgeoff/atc 3.3.1 → 3.4.0
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/package.json +4 -2
- package/src/agents/agent-adapter.ts +10 -0
- package/src/agents/build-cli-argv.ts +2 -4
- package/src/agents/claude-adapter.ts +13 -3
- package/src/agents/codex-adapter.ts +11 -2
- package/src/agents/gateway-adapter.ts +20 -8
- package/src/agents/grok-adapter.ts +16 -13
- package/src/agents/load-claude-config-bundle.ts +6 -6
- package/src/agents/print-codex-hook-file.ts +7 -3
- package/src/agents/print-grok-hook-file.ts +8 -3
- package/src/agents/resolve-agent-home.ts +7 -3
- package/src/agents/resolve-claude-global-config-path.ts +4 -4
- package/src/agents/update-claude-project-trust.ts +41 -30
- package/src/agents/with-claude-config-lock.ts +13 -1
- package/src/agents/write-atc-bridge.ts +10 -7
- package/src/agents/write-hook-settings.ts +13 -7
- package/src/client/boot-daemon.ts +48 -15
- package/src/client/dirs.ts +4 -2
- package/src/client/index.ts +17 -1
- package/src/client/log-client-event.ts +20 -0
- package/src/client/spawn-picker.ts +200 -121
- package/src/client/ui.ts +7 -7
- package/src/clients.ts +41 -11
- package/src/daemon/daemon.ts +143 -33
- package/src/daemon/event-signal.ts +19 -3
- package/src/daemon/make-hook-runner.ts +82 -22
- package/src/daemon/permission-registry.ts +11 -6
- package/src/daemon/restore-fleet.ts +62 -17
- package/src/daemon/run-eject-handoff.ts +20 -2
- package/src/daemon/sessions.ts +6 -0
- package/src/daemon/start-tcp-listener.ts +12 -10
- package/src/federation/daemon-caller.ts +25 -4
- package/src/federation/daemon-pool.ts +3 -1
- package/src/federation/open-gateway-caller.ts +6 -1
- package/src/grants.ts +36 -11
- package/src/hook-report.ts +17 -3
- package/src/mcp/read-report-texts.ts +11 -7
- package/src/mcp/start-mcp-http-server.ts +5 -1
- package/src/mcp-http-server.ts +85 -32
- package/src/report.ts +21 -3
- package/src/run-bridge-tap.ts +36 -7
- package/src/run-config-migrate.ts +35 -11
- package/src/run-gateway.ts +4 -2
- package/src/shared/build-atc-command.ts +7 -3
- package/src/shared/bun-sqlite-driver.ts +10 -0
- package/src/shared/claim-daemon-lock.ts +10 -4
- package/src/shared/collect-dir-roots.ts +6 -6
- package/src/shared/collect-hooks.ts +7 -6
- package/src/shared/config.ts +19 -9
- package/src/shared/system-clock.ts +24 -0
- package/src/sources/github/collect-github-repos.ts +1 -1
- package/src/sources/github/read-git-protocol.ts +1 -1
- package/src/sources/github/run-gh.ts +15 -9
- package/src/store/state-store.ts +57 -7
- package/src/tap.ts +44 -13
- package/src/test-utils/build-mock-agent-adapter.ts +25 -0
- package/src/test-utils/build-mock-agent-entry.ts +32 -0
- package/src/test-utils/build-mock-auth-binding.ts +28 -0
- package/src/test-utils/build-mock-auth-profile.ts +62 -0
- package/src/test-utils/build-mock-daemon-record.ts +26 -0
- package/src/test-utils/build-mock-fleet-entry.ts +33 -0
- package/src/test-utils/build-mock-gateway-config.ts +32 -0
- package/src/test-utils/build-mock-imp-identity.ts +18 -0
- package/src/test-utils/build-mock-key-binding-claim.ts +34 -0
- package/src/test-utils/build-mock-message-record.ts +22 -0
- package/src/test-utils/build-mock-mirror-session.ts +42 -0
- package/src/test-utils/build-mock-registry-daemon.ts +28 -0
- package/src/test-utils/build-mock-session-descriptor.ts +66 -0
- package/src/test-utils/build-mock-session.ts +43 -0
- package/src/test-utils/build-mock-sortable-session-view.ts +20 -0
- package/src/test-utils/build-stub-attention-adapter.ts +22 -0
- package/src/test-utils/build-stub-bridge-context.ts +20 -0
- package/src/test-utils/build-stub-brokered-agent-adapter.ts +96 -0
- package/src/test-utils/build-stub-channel-opener.ts +45 -0
- package/src/test-utils/build-stub-claude-headless-run.ts +13 -0
- package/src/test-utils/build-stub-client-screens.ts +33 -0
- package/src/test-utils/build-stub-clock.ts +67 -0
- package/src/test-utils/build-stub-daemon-context.ts +81 -0
- package/src/test-utils/build-stub-daemon-requests.ts +103 -0
- package/src/test-utils/{fixture-dir-provider.ts → build-stub-dir-provider.ts} +20 -12
- package/src/test-utils/build-stub-execution-provider.ts +112 -0
- package/src/test-utils/build-stub-fleet-caller.ts +60 -0
- package/src/test-utils/build-stub-forking-gh.ts +10 -0
- package/src/test-utils/build-stub-gh.ts +50 -0
- package/src/test-utils/build-stub-handoff-daemon.ts +11 -0
- package/src/test-utils/build-stub-harness-relay.ts +50 -0
- package/src/test-utils/build-stub-headless-runner.ts +60 -0
- package/src/test-utils/build-stub-held-gh.ts +19 -0
- package/src/test-utils/build-stub-held-inbox-source.ts +36 -0
- package/src/test-utils/build-stub-held-provider.ts +58 -0
- package/src/test-utils/build-stub-held-zoxide.ts +13 -0
- package/src/test-utils/build-stub-host-hold.ts +31 -0
- package/src/test-utils/build-stub-host-provider.ts +26 -0
- package/src/test-utils/{fixture-imp-port.ts → build-stub-imp-port.ts} +119 -63
- package/src/test-utils/build-stub-log.ts +15 -0
- package/src/test-utils/build-stub-mcp-claude.ts +26 -0
- package/src/test-utils/build-stub-mcp-grok.ts +19 -0
- package/src/test-utils/build-stub-peer-socket.ts +81 -0
- package/src/test-utils/build-stub-picker-host.ts +36 -0
- package/src/test-utils/build-stub-proxied-agent-adapter.ts +29 -0
- package/src/test-utils/build-stub-pty-provider.ts +53 -0
- package/src/test-utils/build-stub-recording-atc.ts +10 -0
- package/src/test-utils/build-stub-signed-in-gh.ts +18 -0
- package/src/test-utils/build-stub-signed-out-gh.ts +8 -0
- package/src/test-utils/build-stub-soft-kill-provider.ts +29 -0
- package/src/test-utils/build-stub-targets.ts +58 -0
- package/src/test-utils/build-stub-terminal.ts +19 -0
- package/src/test-utils/build-stub-timeout-scheduler.ts +48 -0
- package/src/test-utils/build-stub-tui-claude.ts +47 -0
- package/src/test-utils/build-stub-tui-grok.ts +53 -0
- package/src/test-utils/build-stub-wait-clock.ts +23 -0
- package/src/test-utils/build-target-options-from-config.ts +37 -0
- package/src/test-utils/collect-process-tree.ts +75 -0
- package/src/test-utils/create-git-fixture.ts +120 -0
- package/src/test-utils/create-migrated-state-db.ts +12 -10
- package/src/test-utils/create-stub-bin.ts +19 -0
- package/src/test-utils/create-stub-claude.ts +75 -0
- package/src/test-utils/create-stub-codex.ts +31 -0
- package/src/test-utils/create-stub-composer.ts +65 -0
- package/src/test-utils/create-stub-failing-agent-adapter.ts +111 -0
- package/src/test-utils/create-stub-grok.ts +44 -0
- package/src/test-utils/create-stub-guest-clis.ts +55 -0
- package/src/test-utils/create-stub-harness-guest.ts +35 -0
- package/src/test-utils/create-stub-recording-claude.ts +20 -0
- package/src/test-utils/{setup-fake-systemd.ts → create-stub-systemd.ts} +5 -5
- package/src/test-utils/get-gateway-config.ts +3 -3
- package/src/test-utils/get-only-effect-ref.ts +21 -0
- package/src/test-utils/get-only-imp-name.ts +19 -0
- package/src/test-utils/get-records.ts +20 -0
- package/src/test-utils/get-string.ts +14 -0
- package/src/test-utils/keys.ts +26 -0
- package/src/test-utils/merge-deep.ts +36 -0
- package/src/test-utils/mock-overrides.ts +13 -0
- package/src/test-utils/open-repo-step.ts +24 -0
- package/src/test-utils/read-query-plan.ts +22 -0
- package/src/test-utils/resolve-atc-command.ts +13 -0
- package/src/test-utils/resolve-gateway-command.ts +13 -0
- package/src/test-utils/run-atc.ts +56 -0
- package/src/test-utils/run-legacy-daemon.ts +27 -5
- package/src/test-utils/run-listener-daemon.ts +5 -10
- package/src/test-utils/run-source-daemon.ts +2 -2
- package/src/test-utils/seed-faker.ts +7 -0
- package/src/test-utils/send-lines-before-handshake.ts +35 -0
- package/src/test-utils/send-mcp-request.ts +25 -0
- package/src/test-utils/setup-mcp-home.ts +99 -0
- package/src/test-utils/setup-mcp-http.ts +19 -31
- package/src/test-utils/spawn-claude-session.ts +34 -0
- package/src/test-utils/spawn-grok-session.ts +39 -0
- package/src/test-utils/start-cut-proxy.ts +25 -8
- package/src/test-utils/start-daemon-process.ts +192 -0
- package/src/test-utils/start-git-http-server.ts +21 -3
- package/src/test-utils/start-mcp-stdio.ts +195 -0
- package/src/test-utils/start-output-capture.ts +20 -0
- package/src/test-utils/start-stub-dropping-daemon.ts +103 -0
- package/src/test-utils/start-stub-impd-info.ts +32 -0
- package/src/test-utils/start-stub-impd.ts +138 -0
- package/src/test-utils/start-stub-legacy-daemon.ts +166 -0
- package/src/test-utils/start-stub-mcp-server.ts +50 -0
- package/src/test-utils/start-stub-refusing-listener.ts +38 -0
- package/src/test-utils/start-stub-reporter-socket.ts +77 -0
- package/src/test-utils/start-stub-restore-daemon.ts +58 -0
- package/src/test-utils/start-stub-session-bridge.ts +98 -0
- package/src/test-utils/start-stub-silent-server.ts +33 -0
- package/src/test-utils/start-stub-source-daemon.ts +39 -0
- package/src/test-utils/start-stub-stalled-listener.ts +40 -0
- package/src/test-utils/start-stub-tap.ts +38 -0
- package/src/test-utils/start-stub-tcp-daemon.ts +70 -0
- package/src/test-utils/start-stub-telemetry-collector.ts +26 -0
- package/src/test-utils/start-test-daemon.ts +215 -0
- package/src/test-utils/start-tui-harness.ts +284 -0
- package/src/test-utils/subscribe-to-socket-lines.ts +59 -12
- package/src/test-utils/try-send-request.ts +29 -0
- package/src/test-utils/wait-for-event.ts +41 -0
- package/src/test-utils/wait-for.ts +15 -3
- package/src/verify-restored-fleet.ts +30 -17
- package/src/workspace/check-repository-access.ts +4 -0
- package/src/workspace/collect-remote-refs.ts +4 -2
- package/src/workspace/run-git.ts +6 -0
- package/src/test-utils/start-legacy-daemon.ts +0 -111
|
@@ -34,6 +34,18 @@ type PickerStep =
|
|
|
34
34
|
| 'prompt'
|
|
35
35
|
| 'spawned';
|
|
36
36
|
|
|
37
|
+
// The kinds of answer the flow can drop: a git source's listing, a
|
|
38
|
+
// directory source's listing, each request a step waits on, the daemon's
|
|
39
|
+
// targets and sources, and the local directory flow's listing.
|
|
40
|
+
export type DroppedAnswer =
|
|
41
|
+
| 'listing'
|
|
42
|
+
| 'directory listing'
|
|
43
|
+
| 'interpret'
|
|
44
|
+
| 'probe'
|
|
45
|
+
| 'spawn'
|
|
46
|
+
| 'targets and sources'
|
|
47
|
+
| 'local directory listing';
|
|
48
|
+
|
|
37
49
|
// What the flow borrows from the client that owns the screen, the daemon
|
|
38
50
|
// connection and the fleet mirror.
|
|
39
51
|
export interface SpawnPickerDeps<TMirror> {
|
|
@@ -50,6 +62,27 @@ export interface SpawnPickerDeps<TMirror> {
|
|
|
50
62
|
readonly toMirrorSession: (value: unknown) => TMirror | null;
|
|
51
63
|
readonly upsertMirror: (session: Readonly<TMirror>) => void;
|
|
52
64
|
readonly hasDaemonFeature: (feature: DaemonFeature) => boolean;
|
|
65
|
+
|
|
66
|
+
// Where the flow draws: every byte of the picker's screen goes through it.
|
|
67
|
+
readonly write: (chunk: string) => void;
|
|
68
|
+
|
|
69
|
+
// The client's own directory, which the directory step lists first.
|
|
70
|
+
readonly cwd: string;
|
|
71
|
+
|
|
72
|
+
// The config file the flow reads its agents, roots, and workspaces from
|
|
73
|
+
// each time it opens.
|
|
74
|
+
readonly configPath: string;
|
|
75
|
+
|
|
76
|
+
// Called each time the flow drops an answer, with the kind of answer it
|
|
77
|
+
// dropped: one for a flow or a request that has since moved on, which
|
|
78
|
+
// changes nothing, or a listing for a source the flow has left, which
|
|
79
|
+
// fills nothing and only clears the pending listing so that source lists
|
|
80
|
+
// again when its step reopens.
|
|
81
|
+
readonly onDropAnswer?: (kind: DroppedAnswer) => void;
|
|
82
|
+
|
|
83
|
+
// Records any other decision the flow takes without drawing anything,
|
|
84
|
+
// such as text it ignores on a step that takes none.
|
|
85
|
+
readonly log?: (line: string) => void;
|
|
53
86
|
}
|
|
54
87
|
|
|
55
88
|
// One source `agents.list` returned, in the order the daemon offers them.
|
|
@@ -279,7 +312,7 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
279
312
|
this.generation += 1;
|
|
280
313
|
this.resume = resume;
|
|
281
314
|
|
|
282
|
-
const config = loadConfig();
|
|
315
|
+
const config = loadConfig(this.deps.configPath);
|
|
283
316
|
|
|
284
317
|
this.picks = collectAgentPicks(config);
|
|
285
318
|
this.roots = config.dirs.roots;
|
|
@@ -315,7 +348,7 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
315
348
|
this.refusal = null;
|
|
316
349
|
this.pending = null;
|
|
317
350
|
|
|
318
|
-
|
|
351
|
+
this.deps.write(ansi.clear);
|
|
319
352
|
|
|
320
353
|
// One installed agent is no choice to make, so the flow goes on with it.
|
|
321
354
|
if (this.picks.length === 1) {
|
|
@@ -439,13 +472,16 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
439
472
|
hint = 'reading targets and sources… · esc cancel';
|
|
440
473
|
}
|
|
441
474
|
|
|
442
|
-
drawPicker(
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
475
|
+
drawPicker(
|
|
476
|
+
{
|
|
477
|
+
title: `${verb}: agent`,
|
|
478
|
+
items: this.picks.map((p) => p.label),
|
|
479
|
+
selected: this.selected,
|
|
480
|
+
input: this.input,
|
|
481
|
+
hint,
|
|
482
|
+
},
|
|
483
|
+
this.deps.write,
|
|
484
|
+
);
|
|
449
485
|
} else if (this.step === 'dir') {
|
|
450
486
|
this.renderDirStep(verb);
|
|
451
487
|
} else if (this.step === 'source') {
|
|
@@ -457,36 +493,45 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
457
493
|
} else if (this.step === 'confirm') {
|
|
458
494
|
this.renderConfirmStep();
|
|
459
495
|
} else if (this.step === 'spawned') {
|
|
460
|
-
drawPicker(
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
496
|
+
drawPicker(
|
|
497
|
+
{
|
|
498
|
+
title: `${verb}: started`,
|
|
499
|
+
items: this.spawnWarnings.flatMap((w) => splitToWidth(w, getPickerWidth() - 4)),
|
|
500
|
+
selected: -1,
|
|
501
|
+
input: '',
|
|
502
|
+
hint: '⏎ attach · esc back',
|
|
503
|
+
},
|
|
504
|
+
this.deps.write,
|
|
505
|
+
);
|
|
467
506
|
} else if (this.step === 'name') {
|
|
468
507
|
const where = this.isGitFlow() ? this.formatDestination() : formatDir(this.dir);
|
|
469
508
|
|
|
470
|
-
drawPicker(
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
509
|
+
drawPicker(
|
|
510
|
+
{
|
|
511
|
+
title: `${verb}: name`,
|
|
512
|
+
items: [],
|
|
513
|
+
selected: -1,
|
|
514
|
+
input: this.input,
|
|
515
|
+
placeholder: formatDirName(this.autoDestination ?? this.dir),
|
|
516
|
+
hint: this.refusal ?? `session name for ${where} · ⏎ accept · esc back`,
|
|
517
|
+
},
|
|
518
|
+
this.deps.write,
|
|
519
|
+
);
|
|
478
520
|
} else {
|
|
479
|
-
drawPicker(
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
521
|
+
drawPicker(
|
|
522
|
+
{
|
|
523
|
+
title: 'spawn: initial prompt',
|
|
524
|
+
items: [],
|
|
525
|
+
selected: -1,
|
|
526
|
+
input: this.input,
|
|
527
|
+
placeholder: 'optional — ⏎ to start interactive',
|
|
528
|
+
hint:
|
|
529
|
+
this.pending?.label ??
|
|
530
|
+
this.refusal ??
|
|
531
|
+
'first message for the session · ⏎ spawn · esc back',
|
|
532
|
+
},
|
|
533
|
+
this.deps.write,
|
|
534
|
+
);
|
|
490
535
|
}
|
|
491
536
|
|
|
492
537
|
this.deps.scheduleStatus();
|
|
@@ -518,16 +563,19 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
518
563
|
|
|
519
564
|
this.selected = Math.min(this.selected, Math.max(0, Math.min(items.length, 10) - 1));
|
|
520
565
|
|
|
521
|
-
drawPicker(
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
566
|
+
drawPicker(
|
|
567
|
+
{
|
|
568
|
+
title: `${verb}: ${this.findSource()?.label ?? 'directory'}`,
|
|
569
|
+
items,
|
|
570
|
+
selected: this.selected,
|
|
571
|
+
input: this.input,
|
|
572
|
+
hint:
|
|
573
|
+
this.pending?.label ??
|
|
574
|
+
this.refusal ??
|
|
575
|
+
`type to filter, or a path (/ ~ .) · ↑↓ move · ⏎ select${this.formatTabHint()} · esc cancel`,
|
|
576
|
+
},
|
|
577
|
+
this.deps.write,
|
|
578
|
+
);
|
|
531
579
|
}
|
|
532
580
|
|
|
533
581
|
private renderTargetStep(verb: string) {
|
|
@@ -544,18 +592,21 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
544
592
|
hint = `${gap} · set targets in config.json · esc back`;
|
|
545
593
|
}
|
|
546
594
|
|
|
547
|
-
drawPicker(
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
595
|
+
drawPicker(
|
|
596
|
+
{
|
|
597
|
+
title: `${verb}: target`,
|
|
598
|
+
items: this.targets.map((t) => formatTargetPick(t)),
|
|
599
|
+
selected: this.selected,
|
|
600
|
+
input: '',
|
|
601
|
+
hint: this.refusal ?? hint,
|
|
602
|
+
dimmed: new Set(
|
|
603
|
+
this.targets.flatMap((t, i) =>
|
|
604
|
+
t.takesWorkspace || (!git && t.available && t.inPlace) ? [] : [i],
|
|
605
|
+
),
|
|
556
606
|
),
|
|
557
|
-
|
|
558
|
-
|
|
607
|
+
},
|
|
608
|
+
this.deps.write,
|
|
609
|
+
);
|
|
559
610
|
}
|
|
560
611
|
|
|
561
612
|
private renderSourceStep() {
|
|
@@ -566,18 +617,21 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
566
617
|
const scope = this.listedScope === null ? '' : ` · ${this.listedScope}`;
|
|
567
618
|
const listing = this.listingSeq === null ? null : 'listing… · esc stops';
|
|
568
619
|
|
|
569
|
-
drawPicker(
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
620
|
+
drawPicker(
|
|
621
|
+
{
|
|
622
|
+
title: `spawn: ${this.findSource()?.label ?? ''}${scope}`,
|
|
623
|
+
items: items.map((item) => item.label),
|
|
624
|
+
selected: items.length === 0 || items[0]?.notice === true ? -1 : this.selected,
|
|
625
|
+
input: this.input,
|
|
626
|
+
hint:
|
|
627
|
+
this.pending?.label ??
|
|
628
|
+
this.refusal ??
|
|
629
|
+
listing ??
|
|
630
|
+
`type to filter, or a git URL · ↑↓ move · ⏎ select${this.formatTabHint()} · esc back`,
|
|
631
|
+
dimmed: new Set(items.flatMap((item, i) => (item.notice === true ? [i] : []))),
|
|
632
|
+
},
|
|
633
|
+
this.deps.write,
|
|
634
|
+
);
|
|
581
635
|
}
|
|
582
636
|
|
|
583
637
|
private renderRefStep() {
|
|
@@ -585,16 +639,19 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
585
639
|
|
|
586
640
|
this.selected = Math.min(this.selected, Math.max(0, Math.min(items.length, 10) - 1));
|
|
587
641
|
|
|
588
|
-
drawPicker(
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
642
|
+
drawPicker(
|
|
643
|
+
{
|
|
644
|
+
title: `spawn: ref · ${this.repo?.label ?? ''}`,
|
|
645
|
+
items: items.map((item) => formatRef(item, this.repo?.head ?? null)),
|
|
646
|
+
selected: items.length === 0 ? -1 : this.selected,
|
|
647
|
+
input: this.input,
|
|
648
|
+
hint:
|
|
649
|
+
this.pending?.label ??
|
|
650
|
+
this.refusal ??
|
|
651
|
+
'branch, tag, or full commit id · ↑↓ move · ⏎ select · esc back',
|
|
652
|
+
},
|
|
653
|
+
this.deps.write,
|
|
654
|
+
);
|
|
598
655
|
}
|
|
599
656
|
|
|
600
657
|
private renderConfirmStep() {
|
|
@@ -606,23 +663,26 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
606
663
|
refRow = ref.ref === null ? `commit ${ref.sha}` : `${ref.ref} → ${ref.sha.slice(0, 12)}`;
|
|
607
664
|
}
|
|
608
665
|
|
|
609
|
-
drawPicker(
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
666
|
+
drawPicker(
|
|
667
|
+
{
|
|
668
|
+
title: 'spawn: confirm',
|
|
669
|
+
items: [
|
|
670
|
+
`source ${this.repo?.url ?? ''}`,
|
|
671
|
+
`ref ${refRow}`,
|
|
672
|
+
`target ${target === null ? 'default' : `${target.id} (${target.provider})`}`,
|
|
673
|
+
`dest ${this.formatDestination()}`,
|
|
674
|
+
`agent ${this.agent}`,
|
|
675
|
+
],
|
|
676
|
+
selected: -1,
|
|
677
|
+
input: this.input,
|
|
678
|
+
placeholder:
|
|
679
|
+
this.autoDestination === null
|
|
680
|
+
? 'destination on the target'
|
|
681
|
+
: 'picked on the target · or type an absolute path',
|
|
682
|
+
hint: this.refusal ?? 'destination on the target · ⏎ continue · esc back',
|
|
683
|
+
},
|
|
684
|
+
this.deps.write,
|
|
685
|
+
);
|
|
626
686
|
}
|
|
627
687
|
|
|
628
688
|
// The source the flow is on, or null on the local directory flow.
|
|
@@ -652,6 +712,8 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
652
712
|
private applyInput(value: string) {
|
|
653
713
|
// The target step takes no text: its list is short and fixed.
|
|
654
714
|
if (this.step === 'target') {
|
|
715
|
+
this.deps.log?.('ignored text on the target step');
|
|
716
|
+
|
|
655
717
|
return;
|
|
656
718
|
}
|
|
657
719
|
|
|
@@ -692,7 +754,7 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
692
754
|
|
|
693
755
|
this.refusal = 'cancelled · esc back';
|
|
694
756
|
|
|
695
|
-
|
|
757
|
+
this.deps.write(ansi.clear);
|
|
696
758
|
this.render();
|
|
697
759
|
}
|
|
698
760
|
|
|
@@ -758,7 +820,7 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
758
820
|
this.step = 'name';
|
|
759
821
|
}
|
|
760
822
|
|
|
761
|
-
|
|
823
|
+
this.deps.write(ansi.clear);
|
|
762
824
|
this.render();
|
|
763
825
|
}
|
|
764
826
|
|
|
@@ -779,7 +841,7 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
779
841
|
this.sourcesPending = false;
|
|
780
842
|
this.step = 'agent';
|
|
781
843
|
|
|
782
|
-
|
|
844
|
+
this.deps.write(ansi.clear);
|
|
783
845
|
this.render();
|
|
784
846
|
}
|
|
785
847
|
|
|
@@ -787,7 +849,7 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
787
849
|
// has one, else to the agent.
|
|
788
850
|
private applySourceCancel() {
|
|
789
851
|
if (this.openTargetStep()) {
|
|
790
|
-
|
|
852
|
+
this.deps.write(ansi.clear);
|
|
791
853
|
this.render();
|
|
792
854
|
|
|
793
855
|
return;
|
|
@@ -804,7 +866,7 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
804
866
|
if (!this.isGitFlow()) {
|
|
805
867
|
this.step = 'dir';
|
|
806
868
|
|
|
807
|
-
|
|
869
|
+
this.deps.write(ansi.clear);
|
|
808
870
|
this.render();
|
|
809
871
|
|
|
810
872
|
return;
|
|
@@ -889,7 +951,7 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
889
951
|
return;
|
|
890
952
|
}
|
|
891
953
|
|
|
892
|
-
|
|
954
|
+
this.deps.write(ansi.clear);
|
|
893
955
|
this.render();
|
|
894
956
|
}
|
|
895
957
|
|
|
@@ -899,7 +961,7 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
899
961
|
const typed = this.input.trim();
|
|
900
962
|
|
|
901
963
|
const chosen =
|
|
902
|
-
this.collectDirItems()[this.selected] ?? resolvePathInput(typed,
|
|
964
|
+
this.collectDirItems()[this.selected] ?? resolvePathInput(typed, this.deps.cwd, homedir());
|
|
903
965
|
|
|
904
966
|
if (chosen !== null) {
|
|
905
967
|
this.applyDir(chosen);
|
|
@@ -920,7 +982,7 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
920
982
|
this.step = 'name';
|
|
921
983
|
}
|
|
922
984
|
|
|
923
|
-
|
|
985
|
+
this.deps.write(ansi.clear);
|
|
924
986
|
this.render();
|
|
925
987
|
}
|
|
926
988
|
|
|
@@ -957,7 +1019,7 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
957
1019
|
|
|
958
1020
|
this.step = 'name';
|
|
959
1021
|
|
|
960
|
-
|
|
1022
|
+
this.deps.write(ansi.clear);
|
|
961
1023
|
this.render();
|
|
962
1024
|
}
|
|
963
1025
|
|
|
@@ -966,9 +1028,9 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
966
1028
|
* else fuzzy-filters the merged list.
|
|
967
1029
|
*/
|
|
968
1030
|
private collectDirItems(): string[] {
|
|
969
|
-
return resolvePathInput(this.input,
|
|
1031
|
+
return resolvePathInput(this.input, this.deps.cwd, homedir()) === null
|
|
970
1032
|
? pickMatches(this.dirs, this.input)
|
|
971
|
-
: collectPathCompletions(this.input,
|
|
1033
|
+
: collectPathCompletions(this.input, this.deps.cwd, homedir());
|
|
972
1034
|
}
|
|
973
1035
|
|
|
974
1036
|
// The git source step's rows: the other URL form after a failed probe,
|
|
@@ -1029,6 +1091,8 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
1029
1091
|
const sources = this.sources ?? [];
|
|
1030
1092
|
|
|
1031
1093
|
if (sources.length < 2) {
|
|
1094
|
+
this.deps.log?.('ignored tab with one source');
|
|
1095
|
+
|
|
1032
1096
|
return;
|
|
1033
1097
|
}
|
|
1034
1098
|
|
|
@@ -1073,6 +1137,8 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
1073
1137
|
}
|
|
1074
1138
|
|
|
1075
1139
|
if (this.isStale(generation) || this.pathListingSeq !== seq) {
|
|
1140
|
+
this.deps.onDropAnswer?.('directory listing');
|
|
1141
|
+
|
|
1076
1142
|
return;
|
|
1077
1143
|
}
|
|
1078
1144
|
|
|
@@ -1093,12 +1159,12 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
1093
1159
|
|
|
1094
1160
|
this.dirLabels = new Map(listed);
|
|
1095
1161
|
|
|
1096
|
-
this.dirs = [...new Set([
|
|
1162
|
+
this.dirs = [...new Set([this.deps.cwd, ...listed.map(([dir]) => dir)])];
|
|
1097
1163
|
this.input = '';
|
|
1098
1164
|
this.selected = 0;
|
|
1099
1165
|
this.step = 'dir';
|
|
1100
1166
|
|
|
1101
|
-
|
|
1167
|
+
this.deps.write(ansi.clear);
|
|
1102
1168
|
this.render();
|
|
1103
1169
|
}
|
|
1104
1170
|
|
|
@@ -1121,7 +1187,7 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
1121
1187
|
}
|
|
1122
1188
|
|
|
1123
1189
|
if (this.openTargetStep()) {
|
|
1124
|
-
|
|
1190
|
+
this.deps.write(ansi.clear);
|
|
1125
1191
|
this.render();
|
|
1126
1192
|
|
|
1127
1193
|
return;
|
|
@@ -1152,7 +1218,7 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
1152
1218
|
this.listNotice = null;
|
|
1153
1219
|
}
|
|
1154
1220
|
|
|
1155
|
-
|
|
1221
|
+
this.deps.write(ansi.clear);
|
|
1156
1222
|
this.render();
|
|
1157
1223
|
|
|
1158
1224
|
// A scope a reading asked for is listed in place of the source's own
|
|
@@ -1209,6 +1275,8 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
1209
1275
|
}
|
|
1210
1276
|
|
|
1211
1277
|
if (this.isStale(generation) || this.listingSeq !== seq) {
|
|
1278
|
+
this.deps.onDropAnswer?.('listing');
|
|
1279
|
+
|
|
1212
1280
|
return;
|
|
1213
1281
|
}
|
|
1214
1282
|
|
|
@@ -1217,6 +1285,7 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
1217
1285
|
if (this.findSource()?.id !== source.id) {
|
|
1218
1286
|
this.listingSeq = null;
|
|
1219
1287
|
this.listedKey = null;
|
|
1288
|
+
this.deps.onDropAnswer?.('listing');
|
|
1220
1289
|
|
|
1221
1290
|
return;
|
|
1222
1291
|
}
|
|
@@ -1242,9 +1311,13 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
1242
1311
|
if (this.step === 'source') {
|
|
1243
1312
|
this.selected = 0;
|
|
1244
1313
|
|
|
1245
|
-
|
|
1314
|
+
this.deps.write(ansi.clear);
|
|
1246
1315
|
this.render();
|
|
1316
|
+
|
|
1317
|
+
return;
|
|
1247
1318
|
}
|
|
1319
|
+
|
|
1320
|
+
this.deps.log?.(`kept the ${this.step} step through a listing answer`);
|
|
1248
1321
|
}
|
|
1249
1322
|
|
|
1250
1323
|
// Takes the git source step's choice: the other URL form on offer, a
|
|
@@ -1317,7 +1390,7 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
1317
1390
|
|
|
1318
1391
|
this.refusal = `no source reads ${typed} · esc back`;
|
|
1319
1392
|
|
|
1320
|
-
|
|
1393
|
+
this.deps.write(ansi.clear);
|
|
1321
1394
|
this.render();
|
|
1322
1395
|
}
|
|
1323
1396
|
|
|
@@ -1424,6 +1497,8 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
1424
1497
|
}
|
|
1425
1498
|
|
|
1426
1499
|
if (this.isStale(generation) || this.pending?.seq !== seq) {
|
|
1500
|
+
this.deps.onDropAnswer?.(kind);
|
|
1501
|
+
|
|
1427
1502
|
return null;
|
|
1428
1503
|
}
|
|
1429
1504
|
|
|
@@ -1452,7 +1527,7 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
1452
1527
|
this.refusal = `${formatError(probed.error)} · esc back`;
|
|
1453
1528
|
this.input = label;
|
|
1454
1529
|
|
|
1455
|
-
|
|
1530
|
+
this.deps.write(ansi.clear);
|
|
1456
1531
|
this.render();
|
|
1457
1532
|
|
|
1458
1533
|
return;
|
|
@@ -1470,7 +1545,7 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
1470
1545
|
this.ref = null;
|
|
1471
1546
|
|
|
1472
1547
|
this.openRefStep();
|
|
1473
|
-
|
|
1548
|
+
this.deps.write(ansi.clear);
|
|
1474
1549
|
this.render();
|
|
1475
1550
|
}
|
|
1476
1551
|
|
|
@@ -1501,7 +1576,7 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
1501
1576
|
this.refusal = `${reason} · ${formatError(probed.error)} · esc back`;
|
|
1502
1577
|
}
|
|
1503
1578
|
|
|
1504
|
-
|
|
1579
|
+
this.deps.write(ansi.clear);
|
|
1505
1580
|
this.render();
|
|
1506
1581
|
}
|
|
1507
1582
|
|
|
@@ -1573,7 +1648,7 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
1573
1648
|
? `the daemon resolved no commit for ${typed} · esc back`
|
|
1574
1649
|
: `${formatError(probed.error)} · esc back`;
|
|
1575
1650
|
|
|
1576
|
-
|
|
1651
|
+
this.deps.write(ansi.clear);
|
|
1577
1652
|
this.render();
|
|
1578
1653
|
}
|
|
1579
1654
|
|
|
@@ -1587,7 +1662,7 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
1587
1662
|
this.input = '';
|
|
1588
1663
|
|
|
1589
1664
|
this.openConfirmStep();
|
|
1590
|
-
|
|
1665
|
+
this.deps.write(ansi.clear);
|
|
1591
1666
|
this.render();
|
|
1592
1667
|
}
|
|
1593
1668
|
|
|
@@ -1764,6 +1839,8 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
1764
1839
|
} catch {}
|
|
1765
1840
|
|
|
1766
1841
|
if (this.isStale(generation)) {
|
|
1842
|
+
this.deps.onDropAnswer?.('targets and sources');
|
|
1843
|
+
|
|
1767
1844
|
return;
|
|
1768
1845
|
}
|
|
1769
1846
|
|
|
@@ -1820,10 +1897,12 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
1820
1897
|
const zoxide = await collectZoxideDirs();
|
|
1821
1898
|
|
|
1822
1899
|
if (this.isStale(generation)) {
|
|
1900
|
+
this.deps.onDropAnswer?.('local directory listing');
|
|
1901
|
+
|
|
1823
1902
|
return;
|
|
1824
1903
|
}
|
|
1825
1904
|
|
|
1826
|
-
this.dirs = collectDirs({ cwd:
|
|
1905
|
+
this.dirs = collectDirs({ cwd: this.deps.cwd, recent, roots: this.roots, zoxide });
|
|
1827
1906
|
|
|
1828
1907
|
this.dirLabels = new Map();
|
|
1829
1908
|
|
|
@@ -1832,7 +1911,7 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
1832
1911
|
this.selected = 0;
|
|
1833
1912
|
this.step = 'dir';
|
|
1834
1913
|
|
|
1835
|
-
|
|
1914
|
+
this.deps.write(ansi.clear);
|
|
1836
1915
|
this.render();
|
|
1837
1916
|
}
|
|
1838
1917
|
|
|
@@ -1882,7 +1961,7 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
1882
1961
|
this.spawnWarnings = warnings.filter((w): w is string => typeof w === 'string');
|
|
1883
1962
|
this.step = 'spawned';
|
|
1884
1963
|
|
|
1885
|
-
|
|
1964
|
+
this.deps.write(ansi.clear);
|
|
1886
1965
|
this.render();
|
|
1887
1966
|
|
|
1888
1967
|
return;
|
|
@@ -1960,7 +2039,7 @@ export class SpawnPicker<TMirror extends { readonly id: string }> {
|
|
|
1960
2039
|
this.input = this.resume ? this.name : prompt;
|
|
1961
2040
|
}
|
|
1962
2041
|
|
|
1963
|
-
|
|
2042
|
+
this.deps.write(ansi.clear);
|
|
1964
2043
|
this.render();
|
|
1965
2044
|
}
|
|
1966
2045
|
|
package/src/client/ui.ts
CHANGED
|
@@ -132,7 +132,7 @@ interface Row {
|
|
|
132
132
|
// no-op, so a stale extent after a full clear is harmless.
|
|
133
133
|
let lastBoxExtent: BoxExtent | null = null;
|
|
134
134
|
|
|
135
|
-
function drawBox(rowsList: readonly Row[]) {
|
|
135
|
+
function drawBox(rowsList: readonly Row[], write: (chunk: string) => void) {
|
|
136
136
|
const boxWidth = Math.max(...rowsList.map((r) => r.width));
|
|
137
137
|
const top = Math.max(1, Math.floor((rows() - 1 - rowsList.length) / 2));
|
|
138
138
|
const left = Math.max(1, Math.floor((cols() - boxWidth) / 2));
|
|
@@ -149,7 +149,7 @@ function drawBox(rowsList: readonly Row[]) {
|
|
|
149
149
|
buf += ansi.moveTo(top + i, left) + r.styled;
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
|
|
152
|
+
write(buf);
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
function boxTop(width: number, title: string): Row {
|
|
@@ -280,7 +280,7 @@ export function buildSessionRow(
|
|
|
280
280
|
};
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
-
export function drawOverlay(view: OverlayView) {
|
|
283
|
+
export function drawOverlay(view: OverlayView, write: (chunk: string) => void = out) {
|
|
284
284
|
const width = Math.min(cols() - 4, 90);
|
|
285
285
|
const rowsList: Row[] = [boxTop(width, 'sessions')];
|
|
286
286
|
|
|
@@ -372,7 +372,7 @@ export function drawOverlay(view: OverlayView) {
|
|
|
372
372
|
|
|
373
373
|
rowsList.push(dimRow(width, hint), boxBottom(width));
|
|
374
374
|
|
|
375
|
-
drawBox(rowsList);
|
|
375
|
+
drawBox(rowsList, write);
|
|
376
376
|
}
|
|
377
377
|
|
|
378
378
|
// A kill acts on the selected session's whole set, so the confirm counts
|
|
@@ -467,7 +467,7 @@ export function drawHelp() {
|
|
|
467
467
|
|
|
468
468
|
rowsList.push(boxDivider(width), dimRow(width, 'esc/? back'), boxBottom(width));
|
|
469
469
|
|
|
470
|
-
drawBox(rowsList);
|
|
470
|
+
drawBox(rowsList, out);
|
|
471
471
|
}
|
|
472
472
|
|
|
473
473
|
export interface PickerView {
|
|
@@ -482,7 +482,7 @@ export interface PickerView {
|
|
|
482
482
|
dimmed?: ReadonlySet<number>;
|
|
483
483
|
}
|
|
484
484
|
|
|
485
|
-
export function drawPicker(view: PickerView) {
|
|
485
|
+
export function drawPicker(view: PickerView, write: (chunk: string) => void = out) {
|
|
486
486
|
const width = getPickerWidth();
|
|
487
487
|
const rowsList: Row[] = [boxTop(width, view.title)];
|
|
488
488
|
const shown = view.items.slice(0, 10);
|
|
@@ -525,7 +525,7 @@ export function drawPicker(view: PickerView) {
|
|
|
525
525
|
boxBottom(width),
|
|
526
526
|
);
|
|
527
527
|
|
|
528
|
-
drawBox(rowsList);
|
|
528
|
+
drawBox(rowsList, write);
|
|
529
529
|
}
|
|
530
530
|
|
|
531
531
|
export function drawHome(fleetCount = 0, leaderLabel = '^Space') {
|