@zgeoff/atc 2.11.0 → 2.12.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.
Files changed (76) hide show
  1. package/package.json +1 -1
  2. package/src/agents/agent-adapter.ts +51 -0
  3. package/src/agents/build-args-without-flags.ts +25 -0
  4. package/src/agents/build-claude-override-args.ts +26 -0
  5. package/src/agents/claude-adapter.ts +49 -2
  6. package/src/agents/claude-effort-levels.ts +5 -0
  7. package/src/agents/codex-adapter.ts +43 -2
  8. package/src/agents/find-flag-value.ts +27 -0
  9. package/src/agents/gateway-adapter.ts +45 -2
  10. package/src/agents/grok-adapter.ts +19 -1
  11. package/src/cli.ts +18 -0
  12. package/src/client/daemon-client.ts +14 -2
  13. package/src/client/spawn-picker.ts +27 -4
  14. package/src/daemon/build-agent-list.ts +37 -1
  15. package/src/daemon/build-config-revision.ts +27 -0
  16. package/src/daemon/build-execution-targets.ts +34 -0
  17. package/src/daemon/build-fleet-events.ts +2 -1
  18. package/src/daemon/build-payload-hash.ts +31 -0
  19. package/src/daemon/build-scoped-context.ts +232 -0
  20. package/src/daemon/build-target-access.ts +33 -0
  21. package/src/daemon/build-target-forbidden-error.ts +13 -0
  22. package/src/daemon/build-target-identity.ts +22 -0
  23. package/src/daemon/build-target-list.ts +50 -0
  24. package/src/daemon/daemon-connection.ts +331 -95
  25. package/src/daemon/daemon.ts +452 -75
  26. package/src/daemon/effect-remains-error.ts +13 -0
  27. package/src/daemon/execution-provider.ts +103 -0
  28. package/src/daemon/find-execution-refusal.ts +104 -0
  29. package/src/daemon/idempotency-ledger.ts +164 -0
  30. package/src/daemon/local-pty-provider.ts +83 -0
  31. package/src/daemon/mint-session-id.ts +5 -5
  32. package/src/daemon/parse-spawn-overrides.ts +94 -0
  33. package/src/daemon/permission-registry.ts +14 -4
  34. package/src/daemon/restore-fleet.ts +66 -19
  35. package/src/daemon/session-runtime.ts +10 -0
  36. package/src/daemon/sessions.ts +282 -65
  37. package/src/daemon/start-headless-run.ts +11 -0
  38. package/src/daemon/start-headless-turn.ts +11 -2
  39. package/src/daemon/target-access.ts +36 -0
  40. package/src/mcp/answer-mcp-request.ts +12 -4
  41. package/src/mcp/answer-rpc-request.ts +28 -1
  42. package/src/mcp/build-principal-caller.ts +15 -0
  43. package/src/mcp/build-spawn-descriptions.ts +45 -0
  44. package/src/mcp/build-tool-list.ts +32 -4
  45. package/src/mcp/mcp-tools.ts +98 -8
  46. package/src/mcp/parse-idempotency-key.ts +29 -0
  47. package/src/mcp/reconnecting-caller.ts +39 -7
  48. package/src/mcp/require-daemon-features.ts +7 -0
  49. package/src/mcp/run-tool.ts +63 -11
  50. package/src/mcp/types.ts +2 -0
  51. package/src/protocol/daemon-error.ts +5 -1
  52. package/src/protocol/daemon-features.ts +24 -0
  53. package/src/protocol/protocol.ts +44 -11
  54. package/src/protocol/request-param-schemas.ts +60 -10
  55. package/src/shared/agent-session-id.ts +1 -1
  56. package/src/shared/collect-principals.ts +51 -0
  57. package/src/shared/collect-targets.ts +144 -0
  58. package/src/shared/config.ts +139 -14
  59. package/src/shared/daemon-id.ts +8 -0
  60. package/src/shared/format-json-kind.ts +21 -0
  61. package/src/shared/sort-json-keys.ts +19 -0
  62. package/src/shared/to-daemon-id.ts +11 -0
  63. package/src/store/fleet-entry.ts +42 -9
  64. package/src/store/idempotency-record.ts +48 -0
  65. package/src/store/message-owner.ts +1 -1
  66. package/src/store/run-migrations.ts +254 -5
  67. package/src/store/state-store.ts +433 -31
  68. package/src/workspace/check-workspace-completeness.ts +90 -0
  69. package/src/workspace/create-workspace-clone.ts +239 -0
  70. package/src/workspace/normalize-git-url.ts +59 -0
  71. package/src/workspace/read-workspace-tar.ts +38 -0
  72. package/src/workspace/resolve-path-source.ts +170 -0
  73. package/src/workspace/run-git.ts +103 -0
  74. package/src/workspace/sanitize-workspace-clone.ts +146 -0
  75. package/src/workspace/workspace-provenance.ts +11 -0
  76. package/src/workspace/workspace-source.ts +8 -0
@@ -0,0 +1,146 @@
1
+ import { mkdir, rm } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { normalizeGitURL } from './normalize-git-url';
4
+ import { runGit } from './run-git';
5
+ import type { WorkspaceProvenance } from './workspace-provenance';
6
+
7
+ interface SanitizedClone {
8
+ readonly ok: true;
9
+ readonly provenance: WorkspaceProvenance;
10
+ }
11
+
12
+ interface SanitizeRefusal {
13
+ readonly ok: false;
14
+ readonly code: 'invalid_git_url' | 'sanitize_failed';
15
+ readonly message: string;
16
+ }
17
+
18
+ /**
19
+ * Strips a fresh clone of everything that could authenticate as, or run code
20
+ * for, the host it was cloned on, before the clone leaves that host. Objects,
21
+ * refs, HEAD, and the checked-out branch stay. The origin URL is reset to its
22
+ * token-free form; every credential setting, every http extra header, every
23
+ * remote URL carrying a credential, and every URL rewrite whose either side holds
24
+ * userinfo are removed from the repository config; hooks, reflogs (which
25
+ * record the clone URL), and any `.git-credentials` or `.netrc` file are
26
+ * deleted. The result is then checked: the history still reads and the
27
+ * config holds no credential.
28
+ */
29
+ export async function sanitizeWorkspaceClone(
30
+ dir: string,
31
+ url: string,
32
+ ): Promise<SanitizeRefusal | SanitizedClone> {
33
+ const normalized = normalizeGitURL(url);
34
+
35
+ if (!normalized.ok) {
36
+ return normalized;
37
+ }
38
+
39
+ const gitDir = join(dir, '.git');
40
+ const configFile = join(gitDir, 'config');
41
+
42
+ for (const key of await collectCredentialKeys(configFile)) {
43
+ await runGit(['config', '--file', configFile, '--unset-all', key]);
44
+ }
45
+
46
+ const setURL = await runGit([
47
+ 'config',
48
+ '--file',
49
+ configFile,
50
+ 'remote.origin.url',
51
+ normalized.url,
52
+ ]);
53
+
54
+ if (setURL.exitCode !== 0) {
55
+ return { ok: false, code: 'sanitize_failed', message: setURL.stderr.trim() };
56
+ }
57
+
58
+ await rm(join(gitDir, 'hooks'), { recursive: true, force: true });
59
+ await mkdir(join(gitDir, 'hooks'));
60
+ await rm(join(gitDir, 'logs'), { recursive: true, force: true });
61
+
62
+ for (const file of ['.git-credentials', '.netrc']) {
63
+ await rm(join(dir, file), { force: true });
64
+ await rm(join(gitDir, file), { force: true });
65
+ }
66
+
67
+ return verifyClone(dir, configFile, normalized.url);
68
+ }
69
+
70
+ /**
71
+ * Every config key whose setting can carry a secret: credential helpers and
72
+ * usernames, http extra headers (where an Authorization header rides),
73
+ * remote URLs that lose something when stripped of credentials, and URL
74
+ * rewrites whose either side holds userinfo.
75
+ */
76
+ async function collectCredentialKeys(configFile: string): Promise<string[]> {
77
+ const listed = await runGit(['config', '--file', configFile, '--list', '-z']);
78
+
79
+ const keys = new Set<string>();
80
+
81
+ for (const entry of listed.stdout.split('\0')) {
82
+ const newline = entry.indexOf('\n');
83
+ const key = newline === -1 ? entry : entry.slice(0, newline);
84
+ const value = newline === -1 ? '' : entry.slice(newline + 1);
85
+
86
+ if (isCredentialEntry(key.toLowerCase(), key, value)) {
87
+ keys.add(key);
88
+ }
89
+ }
90
+
91
+ return [...keys];
92
+ }
93
+
94
+ const URL_WITH_USERINFO = /^[a-z][a-z\d+.-]*:\/\/[^/]*@/iu;
95
+
96
+ function isCredentialEntry(lowered: string, key: string, value: string): boolean {
97
+ if (lowered.startsWith('credential.')) {
98
+ return true;
99
+ }
100
+
101
+ if (lowered.startsWith('http.') && lowered.endsWith('.extraheader')) {
102
+ return true;
103
+ }
104
+
105
+ if (lowered.startsWith('remote.') && /\.(?:url|pushurl)$/u.test(lowered)) {
106
+ const normalized = normalizeGitURL(value);
107
+
108
+ return !normalized.ok || normalized.url !== value;
109
+ }
110
+
111
+ const rewrite = /^url\.(?<base>.+)\.(?:insteadof|pushinsteadof)$/iu.exec(key);
112
+
113
+ if (rewrite !== null) {
114
+ return URL_WITH_USERINFO.test(rewrite.groups?.['base'] ?? '') || URL_WITH_USERINFO.test(value);
115
+ }
116
+
117
+ return false;
118
+ }
119
+
120
+ async function verifyClone(
121
+ dir: string,
122
+ configFile: string,
123
+ url: string,
124
+ ): Promise<SanitizeRefusal | SanitizedClone> {
125
+ const log = await runGit(['log', '-1', '--format=%H'], { cwd: dir });
126
+
127
+ if (log.exitCode !== 0) {
128
+ return {
129
+ ok: false,
130
+ code: 'sanitize_failed',
131
+ message: `history unreadable: ${log.stderr.trim()}`,
132
+ };
133
+ }
134
+
135
+ const remaining = await collectCredentialKeys(configFile);
136
+
137
+ if (remaining.length > 0) {
138
+ return {
139
+ ok: false,
140
+ code: 'sanitize_failed',
141
+ message: `config still holds ${remaining.length} credential entries`,
142
+ };
143
+ }
144
+
145
+ return { ok: true, provenance: { repoURL: url, sha: log.stdout.trim() } };
146
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * What a materialized workspace was built from: the token-free repository
3
+ * URL and the exact commit checked out, plus the image and harness version
4
+ * when the execution target reports them.
5
+ */
6
+ export interface WorkspaceProvenance {
7
+ readonly repoURL: string;
8
+ readonly sha: string;
9
+ readonly image?: string;
10
+ readonly harnessVersion?: string;
11
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Where a session's workspace comes from: a directory on a host, which is
3
+ * resolved to a pushed commit before anything leaves that host, or a git
4
+ * repository and ref that the control side clones itself.
5
+ */
6
+ export type WorkspaceSource =
7
+ | { readonly kind: 'path'; readonly host: string; readonly path: string }
8
+ | { readonly kind: 'git'; readonly url: string; readonly ref: string };