@zgeoff/atc 2.15.0 → 2.15.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.
Files changed (60) hide show
  1. package/package.json +2 -1
  2. package/src/agents/agent-adapter.ts +3 -38
  3. package/src/agents/build-hook-settings.ts +1 -1
  4. package/src/agents/claude-adapter.ts +3 -3
  5. package/src/agents/codex-adapter.ts +3 -3
  6. package/src/agents/gateway-adapter.ts +3 -3
  7. package/src/agents/grok-adapter.ts +3 -3
  8. package/src/agents/start-claude-headless-run.ts +1 -1
  9. package/src/client/boot-daemon.ts +2 -2
  10. package/src/client/collect-agent-picks.ts +1 -1
  11. package/src/client/daemon-client.ts +2 -1
  12. package/src/client/format-overlay-agent-mark.ts +1 -1
  13. package/src/client/index.ts +4 -2
  14. package/src/client/pick-tab-target.ts +2 -2
  15. package/src/client/spawn-picker.ts +1 -1
  16. package/src/client/to-mirror-session.ts +3 -3
  17. package/src/client/ui.ts +3 -3
  18. package/src/daemon/build-message-trail-entry.ts +1 -1
  19. package/src/daemon/build-report-trail-entry.ts +1 -1
  20. package/src/daemon/build-scoped-context.ts +1 -1
  21. package/src/daemon/build-session-message-event.ts +1 -1
  22. package/src/daemon/daemon-connection.ts +4 -237
  23. package/src/daemon/daemon-context.ts +240 -0
  24. package/src/daemon/daemon.ts +9 -5
  25. package/src/daemon/execution-provider.ts +12 -0
  26. package/src/daemon/hooks.ts +1 -7
  27. package/src/daemon/idempotency-ledger.ts +78 -18
  28. package/src/daemon/imp-harness.ts +41 -0
  29. package/src/daemon/local-pty-provider.ts +45 -0
  30. package/src/daemon/parse-hook-line.ts +1 -1
  31. package/src/daemon/sessions.ts +116 -104
  32. package/src/daemon/start-headless-turn.ts +3 -3
  33. package/src/daemon/start-session-bridge.ts +2 -2
  34. package/src/mcp/collect-clients.ts +1 -1
  35. package/src/mcp/collect-grants.ts +1 -1
  36. package/src/mcp/find-client-name.ts +1 -1
  37. package/src/mcp/mcp-auth-schema.ts +37 -0
  38. package/src/mcp/open-mcp-auth.ts +2 -2
  39. package/src/mcp/reconnecting-caller.ts +15 -6
  40. package/src/mcp/remove-client.ts +1 -1
  41. package/src/mcp/revoke-grant.ts +1 -1
  42. package/src/mcp/types.ts +0 -38
  43. package/src/mcp-http-server.ts +2 -1
  44. package/src/protocol/adapter-event.ts +25 -0
  45. package/src/protocol/count-session-states.ts +16 -0
  46. package/src/protocol/daemon-channel.ts +17 -0
  47. package/src/protocol/hook-event.ts +11 -0
  48. package/src/protocol/pinned-group-key.ts +2 -0
  49. package/src/protocol/session-state.ts +5 -0
  50. package/src/protocol/sort-grouped-session-views.ts +29 -0
  51. package/src/protocol/sort-session-views.ts +41 -0
  52. package/src/protocol/sortable-session-view.ts +13 -0
  53. package/src/shared/agent-id.ts +6 -0
  54. package/src/shared/collect-gateways.ts +1 -1
  55. package/src/shared/to-agent-id.ts +11 -0
  56. package/src/store/fleet-entry.ts +2 -2
  57. package/src/store/state-store.ts +5 -4
  58. /package/src/{store → shared}/bun-sqlite-driver.ts +0 -0
  59. /package/src/{agents → shared}/truncate-detail.ts +0 -0
  60. /package/src/{daemon → shared}/truncate-summary.ts +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zgeoff/atc",
3
- "version": "2.15.0",
3
+ "version": "2.15.1",
4
4
  "description": "Terminal control tower for coding-agent sessions",
5
5
  "homepage": "https://github.com/zgeoff/atc#readme",
6
6
  "bugs": "https://github.com/zgeoff/atc/issues",
@@ -24,6 +24,7 @@
24
24
  "build:atc-bridge": "bun scripts/build-atc-bridge.ts && oxfmt src/agents/atc-bridge-files.ts && format-codemod --quiet src/agents/atc-bridge-files.ts",
25
25
  "build:binaries": "bash scripts/build-binaries.sh",
26
26
  "audit": "bun audit",
27
+ "check:imports": "bun scripts/check-imports.ts",
27
28
  "deadcode": "knip",
28
29
  "lint": "oxlint --type-aware --type-check --report-unused-disable-directives-severity error",
29
30
  "lint:fix": "oxlint --type-aware --type-check --report-unused-disable-directives-severity error --fix",
@@ -1,24 +1,9 @@
1
- import type { HookEvent } from '../daemon/hooks';
1
+ import type { AdapterEvent } from '../protocol/adapter-event';
2
+ import type { HookEvent } from '../protocol/hook-event';
3
+ import type { AgentID } from '../shared/agent-id';
2
4
  import type { AgentSessionID } from '../shared/agent-session-id';
3
5
  import type { SessionID } from '../shared/session-id';
4
6
 
5
- /**
6
- * Which agent a session runs under: the key the adapter registry is looked
7
- * up by. Every agent CLI supplies one, and so does every configured backend
8
- * that drives a CLI it does not own, so two ids can share one kind.
9
- */
10
- export type AgentID = string;
11
-
12
- /**
13
- * Missing and empty values become Claude so a fleet written before the agent
14
- * column still restores as Claude. Any other string is returned as it stands,
15
- * registered or not: an id whose adapter is gone must reach the caller intact
16
- * so the session can be shown and refused, never quietly run as Claude.
17
- */
18
- export function toAgentID(raw: unknown): AgentID {
19
- return typeof raw === 'string' && raw !== '' ? raw : 'claude';
20
- }
21
-
22
7
  export interface SpawnOptions {
23
8
  readonly prompt: string;
24
9
 
@@ -64,26 +49,6 @@ export interface GuestSpawnPlan extends SpawnPlan {
64
49
  readonly files: Readonly<Record<string, string>>;
65
50
  }
66
51
 
67
- export interface AdapterEvent {
68
- kind: 'started' | 'needs-input' | 'turn-done' | 'prompt-submitted' | 'ended' | 'heartbeat';
69
- agentSessionID?: AgentSessionID;
70
- message?: string;
71
-
72
- // Fuller activity text than message: what the agent last said or was
73
- // asked, for briefing. Bounded by the adapter.
74
- detail?: string;
75
-
76
- // Opaque handle the adapter can later pull a session name from.
77
- nameSource?: string;
78
-
79
- // Claude resume-existence path. Distinct from nameSource: a naming
80
- // handle is not a resume gate.
81
- transcriptSource?: string;
82
-
83
- // The agent's whole final message for a finished turn; detail holds a bounded preview of it.
84
- result?: string;
85
- }
86
-
87
52
  export interface TranscriptToolUse {
88
53
  readonly name: string;
89
54
  readonly input: string;
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import type { AgentID } from './agent-adapter';
2
+ import type { AgentID } from '../shared/agent-id';
3
3
  import { buildCLICommand } from './build-cli-command';
4
4
 
5
5
  /**
@@ -1,14 +1,15 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { z } from 'zod';
3
- import type { HookEvent } from '../daemon/hooks';
3
+ import type { AdapterEvent } from '../protocol/adapter-event';
4
+ import type { HookEvent } from '../protocol/hook-event';
4
5
  import type { AgentSessionID } from '../shared/agent-session-id';
5
6
  import { buildOptionalString } from '../shared/build-optional-string';
6
7
  import type { Config } from '../shared/config';
7
8
  import { isRecord } from '../shared/report';
8
9
  import { toAgentSessionID } from '../shared/to-agent-session-id';
9
10
  import { toShellArg } from '../shared/to-shell-arg';
11
+ import { truncateDetail } from '../shared/truncate-detail';
10
12
  import type {
11
- AdapterEvent,
12
13
  AgentAdapter,
13
14
  AgentProfile,
14
15
  GuestPaths,
@@ -31,7 +32,6 @@ import type { ClaudeHeadlessRun } from './make-claude-headless-runner';
31
32
  import { parseClaudeTranscriptLine } from './parse-claude-transcript-line';
32
33
  import { planTypedLineInput } from './plan-typed-line-input';
33
34
  import { resolveClaudePermissionMode } from './resolve-claude-permission-mode';
34
- import { truncateDetail } from './truncate-detail';
35
35
  import { writeATCBridge } from './write-atc-bridge';
36
36
  import { writeHookSettings } from './write-hook-settings';
37
37
 
@@ -1,15 +1,16 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { z } from 'zod';
4
- import type { HookEvent } from '../daemon/hooks';
4
+ import type { AdapterEvent } from '../protocol/adapter-event';
5
+ import type { HookEvent } from '../protocol/hook-event';
5
6
  import type { AgentSessionID } from '../shared/agent-session-id';
6
7
  import { buildOptionalString } from '../shared/build-optional-string';
7
8
  import type { Config } from '../shared/config';
8
9
  import { isRecord } from '../shared/report';
9
10
  import { toAgentSessionID } from '../shared/to-agent-session-id';
10
11
  import { toShellArg } from '../shared/to-shell-arg';
12
+ import { truncateDetail } from '../shared/truncate-detail';
11
13
  import type {
12
- AdapterEvent,
13
14
  AgentAdapter,
14
15
  AgentProfile,
15
16
  NameUpdate,
@@ -22,7 +23,6 @@ import { buildArgsWithoutFlags } from './build-args-without-flags';
22
23
  import { findFlagValue } from './find-flag-value';
23
24
  import { planPastedLineInput } from './plan-pasted-line-input';
24
25
  import { resolveAgentHome } from './resolve-agent-home';
25
- import { truncateDetail } from './truncate-detail';
26
26
 
27
27
  // Codex's hook payload keys, snake_case. An absent or wrong-typed field
28
28
  // parses to undefined rather than failing the payload, so a broken reporter
@@ -1,12 +1,12 @@
1
- import type { HookEvent } from '../daemon/hooks';
1
+ import type { AdapterEvent } from '../protocol/adapter-event';
2
+ import type { HookEvent } from '../protocol/hook-event';
3
+ import type { AgentID } from '../shared/agent-id';
2
4
  import type { AgentSessionID } from '../shared/agent-session-id';
3
5
  import type { GatewayConfig } from '../shared/collect-gateways';
4
6
  import type { Config } from '../shared/config';
5
7
  import { toShellArg } from '../shared/to-shell-arg';
6
8
  import type {
7
- AdapterEvent,
8
9
  AgentAdapter,
9
- AgentID,
10
10
  AgentProfile,
11
11
  HeadlessRunner,
12
12
  NameUpdate,
@@ -1,7 +1,8 @@
1
1
  import { existsSync, readFileSync, readdirSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { z } from 'zod';
4
- import type { HookEvent } from '../daemon/hooks';
4
+ import type { AdapterEvent } from '../protocol/adapter-event';
5
+ import type { HookEvent } from '../protocol/hook-event';
5
6
  import type { AgentSessionID } from '../shared/agent-session-id';
6
7
  import { buildOptionalString } from '../shared/build-optional-string';
7
8
  import type { Config } from '../shared/config';
@@ -9,8 +10,8 @@ import { isRecord } from '../shared/report';
9
10
  import type { SessionID } from '../shared/session-id';
10
11
  import { toAgentSessionID } from '../shared/to-agent-session-id';
11
12
  import { toShellArg } from '../shared/to-shell-arg';
13
+ import { truncateDetail } from '../shared/truncate-detail';
12
14
  import type {
13
- AdapterEvent,
14
15
  AgentAdapter,
15
16
  AgentProfile,
16
17
  NameUpdate,
@@ -22,7 +23,6 @@ import type {
22
23
  import { normalizeHookEventName } from './normalize-hook-event';
23
24
  import { planPastedLineInput } from './plan-pasted-line-input';
24
25
  import { resolveAgentHome } from './resolve-agent-home';
25
- import { truncateDetail } from './truncate-detail';
26
26
 
27
27
  interface GrokSessionHookState {
28
28
  latestPromptID?: string;
@@ -1,6 +1,6 @@
1
- import { truncateSummary } from '../daemon/truncate-summary';
2
1
  import { isCompiledBinary } from '../shared/is-compiled-binary';
3
2
  import { isRecord } from '../shared/report';
3
+ import { truncateSummary } from '../shared/truncate-summary';
4
4
  import { buildClaudeQueryOptions } from './build-claude-query-options';
5
5
  import type { ClaudeHeadlessRunRequest } from './build-claude-query-options';
6
6
 
@@ -2,17 +2,17 @@ import { spawn as spawnChild } from 'node:child_process';
2
2
  import type { ChildProcess } from 'node:child_process';
3
3
  import { readFileSync } from 'node:fs';
4
4
  import { join } from 'node:path';
5
- import { toAgentID } from '../agents/agent-adapter';
6
- import type { AgentID } from '../agents/agent-adapter';
7
5
  import { DaemonError } from '../protocol/daemon-error';
8
6
  import type { DaemonFeature } from '../protocol/daemon-features';
9
7
  import { parseDaemonFeatures } from '../protocol/parse-daemon-features';
10
8
  import { PROTOCOL_V } from '../protocol/protocol';
9
+ import type { AgentID } from '../shared/agent-id';
11
10
  import { daemonPidFile, daemonRecordFile, daemonSocketPath } from '../shared/config';
12
11
  import { findDaemonRecord } from '../shared/find-daemon-record';
13
12
  import { getBuild } from '../shared/get-build';
14
13
  import { isCompiledBinary } from '../shared/is-compiled-binary';
15
14
  import { makeSingleFlight } from '../shared/make-single-flight';
15
+ import { toAgentID } from '../shared/to-agent-id';
16
16
  import { DaemonClient } from './daemon-client';
17
17
  import { formatProtocolMismatch } from './format-protocol-mismatch';
18
18
  import type { ProtocolMismatch } from './format-protocol-mismatch';
@@ -1,4 +1,4 @@
1
- import type { AgentID } from '../agents/agent-adapter';
1
+ import type { AgentID } from '../shared/agent-id';
2
2
  import type { Config } from '../shared/config';
3
3
 
4
4
  export interface AgentPick {
@@ -1,3 +1,4 @@
1
+ import type { DaemonChannel } from '../protocol/daemon-channel';
1
2
  import { DaemonError } from '../protocol/daemon-error';
2
3
  import { LineDecoder } from '../protocol/line-decoder';
3
4
  import { OutboundQueue } from '../protocol/outbound-queue';
@@ -14,7 +15,7 @@ interface Pending {
14
15
  * event callback. `open` connects the socket; `sendHello` completes the
15
16
  * handshake and must come first.
16
17
  */
17
- export class DaemonClient {
18
+ export class DaemonClient implements DaemonChannel {
18
19
  onEvent: (event: EventMsg) => void = () => {};
19
20
 
20
21
  onClose: () => void = () => {};
@@ -1,4 +1,4 @@
1
- import type { AgentID } from '../agents/agent-adapter';
1
+ import type { AgentID } from '../shared/agent-id';
2
2
 
3
3
  const BUILT_IN_MARKS: Readonly<Record<AgentID, string>> = { grok: 'g', codex: 'x' };
4
4
 
@@ -1,9 +1,11 @@
1
1
  import { match } from 'ts-pattern';
2
2
  import { createActor } from 'xstate';
3
- import type { AgentID } from '../agents/agent-adapter';
4
- import { countSessionStates, sortGroupedSessionViews, sortSessionViews } from '../daemon/sessions';
3
+ import { countSessionStates } from '../protocol/count-session-states';
5
4
  import { DaemonError } from '../protocol/daemon-error';
6
5
  import type { EventMsg } from '../protocol/protocol';
6
+ import { sortGroupedSessionViews } from '../protocol/sort-grouped-session-views';
7
+ import { sortSessionViews } from '../protocol/sort-session-views';
8
+ import type { AgentID } from '../shared/agent-id';
7
9
  import { loadConfig } from '../shared/config';
8
10
  import { makeSingleFlight } from '../shared/make-single-flight';
9
11
  import { bootDaemonClient } from './boot-daemon';
@@ -1,5 +1,5 @@
1
- import type { SessionState } from '../daemon/sessions';
2
- import { sortSessionViews } from '../daemon/sessions';
1
+ import type { SessionState } from '../protocol/session-state';
2
+ import { sortSessionViews } from '../protocol/sort-session-views';
3
3
 
4
4
  interface TabCandidate {
5
5
  readonly id: string;
@@ -1,6 +1,6 @@
1
1
  import { homedir } from 'node:os';
2
- import type { AgentID } from '../agents/agent-adapter';
3
2
  import { DaemonError } from '../protocol/daemon-error';
3
+ import type { AgentID } from '../shared/agent-id';
4
4
  import { loadConfig } from '../shared/config';
5
5
  import { collectAgentPicks } from './collect-agent-picks';
6
6
  import type { AgentPick } from './collect-agent-picks';
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
- import { toAgentID } from '../agents/agent-adapter';
3
- import type { AgentID } from '../agents/agent-adapter';
4
- import type { SessionState } from '../daemon/sessions';
2
+ import type { SessionState } from '../protocol/session-state';
3
+ import type { AgentID } from '../shared/agent-id';
4
+ import { toAgentID } from '../shared/to-agent-id';
5
5
 
6
6
  export interface MirrorSession {
7
7
  id: string;
package/src/client/ui.ts CHANGED
@@ -1,6 +1,6 @@
1
- import type { AgentID } from '../agents/agent-adapter';
2
- import { PINNED_GROUP_KEY } from '../daemon/sessions';
3
- import type { SessionState } from '../daemon/sessions';
1
+ import { PINNED_GROUP_KEY } from '../protocol/pinned-group-key';
2
+ import type { SessionState } from '../protocol/session-state';
3
+ import type { AgentID } from '../shared/agent-id';
4
4
  import { RESET_INPUT_MODES } from '../shared/reset-input-modes';
5
5
  import { formatDir } from './dirs';
6
6
  import { formatOverlayAgentMark } from './format-overlay-agent-mark';
@@ -1,6 +1,6 @@
1
- import { truncateDetail } from '../agents/truncate-detail';
2
1
  import type { AgentSessionID } from '../shared/agent-session-id';
3
2
  import type { SessionID } from '../shared/session-id';
3
+ import { truncateDetail } from '../shared/truncate-detail';
4
4
  import type { MessageRecord, MessageStatus } from '../store/message-record';
5
5
  import type { TrailEntry } from '../store/trail-entry';
6
6
 
@@ -1,6 +1,6 @@
1
- import { truncateDetail } from '../agents/truncate-detail';
2
1
  import type { AgentSessionID } from '../shared/agent-session-id';
3
2
  import type { SessionID } from '../shared/session-id';
3
+ import { truncateDetail } from '../shared/truncate-detail';
4
4
  import type { TrailEntry } from '../store/trail-entry';
5
5
  import type { NoteReport } from './parse-report';
6
6
 
@@ -3,7 +3,7 @@ import type { SessionID } from '../shared/session-id';
3
3
  import { toSessionID } from '../shared/to-session-id';
4
4
  import { buildTargetForbiddenError } from './build-target-forbidden-error';
5
5
  import { buildTargetIdentity } from './build-target-identity';
6
- import type { DaemonContext } from './daemon-connection';
6
+ import type { DaemonContext } from './daemon-context';
7
7
  import type { KeyedRequest } from './idempotency-ledger';
8
8
  import type { TargetAccess, TargetGrant } from './target-access';
9
9
 
@@ -1,7 +1,7 @@
1
- import { truncateDetail } from '../agents/truncate-detail';
2
1
  import { PROTOCOL_V } from '../protocol/protocol';
3
2
  import type { EventMsg } from '../protocol/protocol';
4
3
  import type { SessionID } from '../shared/session-id';
4
+ import { truncateDetail } from '../shared/truncate-detail';
5
5
  import type { MessageRecord } from '../store/message-record';
6
6
 
7
7
  /**
@@ -1,5 +1,4 @@
1
1
  import { basename, isAbsolute } from 'node:path';
2
- import type { AgentAdapter, AgentID, SpawnOptions, SpawnOverrides } from '../agents/agent-adapter';
3
2
  import { DaemonError } from '../protocol/daemon-error';
4
3
  import { DAEMON_FEATURES } from '../protocol/daemon-features';
5
4
  import { decodeCursor } from '../protocol/decode-cursor';
@@ -16,248 +15,16 @@ import {
16
15
  encodeMessage,
17
16
  } from '../protocol/protocol';
18
17
  import type { ErrorCode, EventMsg, RequestMsg } from '../protocol/protocol';
19
- import type { SpawnWorkspaceSource } from '../protocol/request-param-schemas';
20
- import type { TargetConfigError } from '../shared/collect-targets';
21
- import type { DaemonID } from '../shared/daemon-id';
22
- import type { MessageID } from '../shared/message-id';
18
+ import type { AgentID } from '../shared/agent-id';
23
19
  import { isRecord } from '../shared/report';
24
20
  import type { SessionID } from '../shared/session-id';
25
21
  import { toSessionID } from '../shared/to-session-id';
26
- import type { FleetEntry } from '../store/fleet-entry';
27
- import type { MessageRecord } from '../store/message-record';
28
- import type { Dims } from './attach-registry';
29
- import type { AgentEntry } from './build-agent-list';
30
- import type { FleetEvent } from './build-fleet-events';
31
22
  import { buildPayloadHash } from './build-payload-hash';
32
- import type { ReportView } from './build-report-view';
33
23
  import { buildScopedContext } from './build-scoped-context';
34
- import type { TargetEntry } from './build-target-list';
35
- import type { KeyedRequest } from './idempotency-ledger';
36
- import type { TranscriptPage, TranscriptPosition } from './load-transcript-page';
24
+ import type { DaemonContext, SpawnParams } from './daemon-context';
25
+ import type { TranscriptPosition } from './load-transcript-page';
37
26
  import { parseSpawnOverrides } from './parse-spawn-overrides';
38
- import type { AnswerResult } from './permission-registry';
39
- import type { ScreenText } from './screen-model';
40
- import type { SessionDescriptor } from './sessions';
41
- import type { TargetAccess, TargetGrant } from './target-access';
42
-
43
- export interface SpawnParams {
44
- readonly cwd: string;
45
- readonly name: string;
46
- readonly prompt: string;
47
- readonly cols: number;
48
- readonly rows: number;
49
- readonly resume: SpawnOptions['resume'];
50
- readonly namedBy: 'user' | 'auto';
51
- readonly agent: AgentID;
52
- readonly parent: SessionID | null;
53
- readonly overrides: SpawnOverrides;
54
- readonly target: string;
55
-
56
- // Where the session's working directory comes from; null runs the
57
- // session in cwd as it stands.
58
- readonly workspace: SpawnWorkspaceSource | null;
59
- }
60
-
61
- interface SessionRecord {
62
- readonly session: SessionDescriptor;
63
- readonly prompt: string | null;
64
- readonly lastActivityAt: number;
65
- readonly pending: { readonly message: string } | null;
66
- readonly result: string | null;
67
- }
68
-
69
- // A transcript page with the file it came from, so a cursor into a replaced
70
- // transcript is distinguishable from one into a grown transcript.
71
- /**
72
- * What `session.forget` answers: the token a forget that destroys a host
73
- * must carry, with the time it stops being taken; or the forgotten session,
74
- * with whether its host was destroyed.
75
- */
76
- type ForgetResult =
77
- | { readonly confirmToken: string; readonly expiresAt: number }
78
- | { readonly forgotten: true; readonly destroyed: boolean };
79
-
80
- interface SessionTranscriptRead {
81
- readonly path: string;
82
- readonly page: TranscriptPage;
83
- }
84
-
85
- export interface DaemonContext {
86
- readonly build: string;
87
- readonly daemonID: DaemonID;
88
- readonly collectSessions: () => SessionDescriptor[];
89
- readonly collectSpawnDirs: () => Promise<string[]>;
90
- readonly collectAgents: () => AgentList;
91
- readonly collectFleet: () => Promise<FleetEntry[]>;
92
- readonly loadLastUsedAgent: () => Promise<AgentID>;
93
- readonly findAdapter: (id: AgentID) => AgentAdapter | null;
94
-
95
- // The targets a principal may use.
96
- readonly buildTargetAccess: (principal: string) => TargetAccess;
97
-
98
- // The target and identity a session is bound to, or null when no
99
- // session holds the id.
100
- readonly findSessionGrant: (id: SessionID) => TargetGrant | null;
101
-
102
- // The identity a target holds now, or null when the config holds no such
103
- // target.
104
- readonly findTargetIdentity: (target: string) => string | null;
105
-
106
- // The ids of a session's sub-sessions.
107
- readonly collectChildIDs: (id: SessionID) => SessionID[];
108
-
109
- // The session a permission request belongs to, answered or not, or null
110
- // for an unknown request.
111
- readonly findPermissionSession: (request: string) => SessionID | null;
112
-
113
- // The target a spawn runs on: the one it names, else the default. Throws
114
- // the refusal for a target the spawn cannot run on, and for a spawn
115
- // without a target when the config gives no default.
116
- readonly resolveSpawnTarget: (requested: string | undefined) => string;
117
-
118
- // Throws the refusal for a target that cannot materialize a workspace:
119
- // one whose provider cannot both transfer an archive and run a command.
120
- readonly requireWorkspaceTarget: (target: string) => void;
121
-
122
- // Runs the plan, which throws the refusal for a spawn it refuses, then
123
- // spawns. Answers with the `session.spawn` ok payload, which a keyed
124
- // retry replays as the first spawn answered it while the access, when
125
- // there is one, still reaches the target the spawn's session was bound to.
126
- readonly spawnSession: (
127
- plan: () => SpawnParams,
128
- keyed: KeyedRequest | null,
129
- access: TargetAccess | null,
130
- ) => Promise<Readonly<Record<string, unknown>>>;
131
- readonly killSession: (id: SessionID) => Promise<boolean>;
132
-
133
- // Forgets a session, or answers with the token a forget that destroys a
134
- // host must carry. Throws the refusal for a token it does not take.
135
- readonly forgetSession: (
136
- id: SessionID,
137
- confirmToken: string | undefined,
138
- ) => Promise<ForgetResult | 'missing'>;
139
- readonly updateSession: (id: SessionID, name?: string, pinned?: boolean) => boolean | 'child_pin';
140
- readonly quitDaemon: () => void;
141
- readonly ackSession: (id: SessionID) => boolean;
142
- readonly buildResumeCommand: (id: SessionID) => string | null;
143
- readonly readSessionScreen: (id: SessionID) => Promise<ScreenText | 'missing' | 'no_screen'>;
144
- readonly answerPermission: (request: string, decision: string) => AnswerResult;
145
- readonly restoreFleet: (cols: number, rows: number) => Promise<number>;
146
- readonly attachSession: (
147
- client: OutputClient,
148
- sessionID: SessionID,
149
- dims: Dims,
150
- ) => 'ok' | 'missing' | 'dead';
151
- readonly detachSession: (client: OutputClient, sessionID: SessionID) => void;
152
- readonly detachClient: (client: OutputClient) => void;
153
- readonly writeSessionInput: (
154
- sessionID: SessionID,
155
- data: string,
156
- ) => 'busy' | 'ok' | 'missing' | 'dead';
157
-
158
- // Types a line into the session and submits it the way the session's
159
- // agent accepts a line, as opposed to the raw bytes the input write takes.
160
- readonly writeSessionLine: (
161
- sessionID: SessionID,
162
- text: string,
163
- ) => 'busy' | 'ok' | 'missing' | 'dead';
164
- readonly ejectSession: (
165
- id: SessionID,
166
- prompt: string,
167
- ) => 'ok' | 'missing' | 'unsupported' | 'no_transcript';
168
- readonly adoptSession: (
169
- id: SessionID,
170
- cols: number,
171
- rows: number,
172
- ) => Promise<'ok' | 'missing' | 'no_transcript'>;
173
- readonly resizeSession: (client: OutputClient, sessionID: SessionID, dims: Dims) => boolean;
174
- readonly resyncClient: (sessionID: SessionID, client: OutputClient) => Promise<void>;
175
- readonly queueBytes?: number;
176
- readonly getEffectiveDims: (sessionID: SessionID) => Dims;
177
- readonly readSessionRecord: (id: SessionID) => Promise<SessionRecord | 'missing'>;
178
- readonly loadSessionTranscript: (
179
- id: SessionID,
180
- from: TranscriptPosition | null,
181
- limit: number,
182
- ) => Promise<SessionTranscriptRead | 'missing' | 'unsupported'>;
183
-
184
- // Reads the trail, leaving out each event of a session outside the access
185
- // when there is one.
186
- readonly readEvents: (
187
- afterID: number | null,
188
- limit: number,
189
- waitMs: number,
190
- sessionID: SessionID | null,
191
- access: TargetAccess | null,
192
- ) => Promise<EventsPage>;
193
-
194
- // One report by the trail id of its event, or null for a trail id that
195
- // holds no report, or whose report's session is outside the access when
196
- // there is one.
197
- readonly readReport: (id: number, access: TargetAccess | null) => Promise<ReportView | null>;
198
-
199
- // Answers with the `session.message` ok payload, which a keyed retry
200
- // replays with the message's current status, or with the refusal.
201
- readonly writeSessionMessage: (
202
- sessionID: SessionID,
203
- from: string,
204
- text: string,
205
- keyed: KeyedRequest | null,
206
- ) => Promise<Readonly<Record<string, unknown>> | MessageRefusal>;
207
- readonly readMessage: (messageID: MessageID, waitMs: number) => Promise<MessageView | null>;
208
- readonly attachTap: (client: TapClient, sessionID: SessionID) => 'ok' | 'missing' | 'unsupported';
209
- readonly ackMessage: (
210
- client: TapClient,
211
- sessionID: SessionID,
212
- messageID: MessageID,
213
- ) => Promise<MessageRecord | 'not_tapping' | 'unknown'>;
214
- }
215
-
216
- // The slice of a connection the attach bookkeeping needs: identity plus the
217
- // ability to receive output events.
218
- export interface OutputClient {
219
- readonly sendOutput: (sessionID: SessionID, event: EventMsg, byteLength: number) => void;
220
- }
221
-
222
- // The `agents.list` answer: the host the daemon runs on, each agent, each
223
- // execution target, what a spawn without either runs with, a digest of
224
- // the target config, and the target config problems the daemon started
225
- // with.
226
- interface AgentList {
227
- readonly daemon: {
228
- readonly hostname: string;
229
- readonly platform: string;
230
- readonly arch: string;
231
- readonly build: string;
232
- };
233
- readonly agents: readonly AgentEntry[];
234
- readonly targets: readonly TargetEntry[];
235
- readonly spawnDefaults: { readonly agent: AgentID; readonly target: string | null };
236
- readonly configRevision: string;
237
- readonly targetErrors: readonly TargetConfigError[];
238
- }
239
-
240
- // One `events.read` answer: the events, and whether more follow them.
241
- interface EventsPage {
242
- readonly events: readonly FleetEvent[];
243
- readonly more: boolean;
244
- }
245
-
246
- // One message as `message.get` reports it: the session it belongs to now,
247
- // every field of the record, and the other messages its turn answered.
248
- interface MessageView {
249
- readonly session: SessionID;
250
- readonly record: MessageRecord;
251
- readonly answeredWith: readonly MessageID[];
252
- }
253
-
254
- // Why a session refuses a message before the daemon accepts it.
255
- export type MessageRefusal = 'missing' | 'dead' | 'unsupported' | 'no_tap';
256
-
257
- // The slice of a connection a tap subscription needs.
258
- export interface TapClient {
259
- readonly sendEvent: (event: EventMsg) => void;
260
- }
27
+ import type { TargetAccess } from './target-access';
261
28
 
262
29
  // The targets a request may use and the namespace of its idempotency keys.
263
30
  interface RequestScope {