@zgeoff/atc 2.3.0 → 2.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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zgeoff/atc",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
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",
|
|
@@ -63,6 +63,8 @@
|
|
|
63
63
|
},
|
|
64
64
|
"overrides": {
|
|
65
65
|
"fast-uri": "3.1.6",
|
|
66
|
+
"hono": ">=4.13.5",
|
|
67
|
+
"js-yaml": ">=4.3.2 <5",
|
|
66
68
|
"qs": "6.16.0"
|
|
67
69
|
},
|
|
68
70
|
"packageManager": "bun@1.3.10"
|
|
@@ -1,15 +1,19 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
1
2
|
import type { AgentID } from './agent-adapter';
|
|
2
3
|
import { buildCLICommand } from './build-cli-command';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* What one agent id needs on top of the shared instrumentation. The
|
|
6
7
|
* environment block and the credential helper point the CLI at a backend
|
|
7
|
-
* other than the default one; both are absent for the stock agent.
|
|
8
|
+
* other than the default one; both are absent for the stock agent. The
|
|
9
|
+
* settings are whatever else the config asked this agent's sessions to start
|
|
10
|
+
* with.
|
|
8
11
|
*/
|
|
9
12
|
export interface HookSettingsProfile {
|
|
10
13
|
readonly id: AgentID;
|
|
11
14
|
readonly env?: Readonly<Record<string, string>>;
|
|
12
15
|
readonly apiKeyHelper?: string;
|
|
16
|
+
readonly settings?: Readonly<Record<string, unknown>>;
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
/**
|
|
@@ -31,17 +35,20 @@ export function buildHookSettings(
|
|
|
31
35
|
{ hooks: [{ type: 'command', command: buildCLICommand('hook-report'), timeout: 5 }] },
|
|
32
36
|
];
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
hooks:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
const own = {
|
|
39
|
+
hooks: mergeSessionHooks(
|
|
40
|
+
{
|
|
41
|
+
// SessionStart carries the session id at spawn/resume time, before any
|
|
42
|
+
// interaction — without it a session only enters the fleet file after
|
|
43
|
+
// its first prompt/notification.
|
|
44
|
+
SessionStart: entry,
|
|
45
|
+
Notification: entry,
|
|
46
|
+
Stop: entry,
|
|
47
|
+
UserPromptSubmit: entry,
|
|
48
|
+
SessionEnd: entry,
|
|
49
|
+
},
|
|
50
|
+
profile.settings?.['hooks'],
|
|
51
|
+
),
|
|
45
52
|
|
|
46
53
|
// Fleet status renders inside Claude Code's own status line; the injected
|
|
47
54
|
// command chains the user's configured statusline first, so mirror their
|
|
@@ -56,4 +63,36 @@ export function buildHookSettings(
|
|
|
56
63
|
: { env: profile.env }),
|
|
57
64
|
...(profile.apiKeyHelper === undefined ? {} : { apiKeyHelper: profile.apiKeyHelper }),
|
|
58
65
|
};
|
|
66
|
+
|
|
67
|
+
// The configured settings fill in around what atc writes and never replace
|
|
68
|
+
// it: a session atc did not instrument is a session the fleet cannot track.
|
|
69
|
+
return { ...profile.settings, ...own };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
type SessionHooks = Readonly<Record<string, readonly unknown[]>>;
|
|
73
|
+
|
|
74
|
+
// One hook list per event name, which is the shape every harness that reads
|
|
75
|
+
// this file takes.
|
|
76
|
+
const CONFIGURED_HOOKS_SCHEMA = z.record(z.string(), z.array(z.unknown()));
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* atc's own reporter plus whatever the config added, per event. The reporter
|
|
80
|
+
* runs first and the configured entries follow it, so an added hook joins the
|
|
81
|
+
* fleet's rather than displacing it. A block that is not hook lists is left out
|
|
82
|
+
* whole, and costs its own entries rather than atc's.
|
|
83
|
+
*/
|
|
84
|
+
function mergeSessionHooks(own: SessionHooks, configured: unknown): SessionHooks {
|
|
85
|
+
const parsed = CONFIGURED_HOOKS_SCHEMA.safeParse(configured);
|
|
86
|
+
|
|
87
|
+
if (!parsed.success) {
|
|
88
|
+
return own;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const merged: Record<string, readonly unknown[]> = { ...own };
|
|
92
|
+
|
|
93
|
+
for (const [event, entries] of Object.entries(parsed.data)) {
|
|
94
|
+
merged[event] = [...(merged[event] ?? []), ...entries];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return merged;
|
|
59
98
|
}
|
|
@@ -99,6 +99,7 @@ export class GatewayAdapter implements AgentAdapter {
|
|
|
99
99
|
...(this.gateway.apiKeyHelper === undefined
|
|
100
100
|
? {}
|
|
101
101
|
: { apiKeyHelper: this.gateway.apiKeyHelper }),
|
|
102
|
+
...(this.gateway.settings === undefined ? {} : { settings: this.gateway.settings }),
|
|
102
103
|
});
|
|
103
104
|
|
|
104
105
|
return this.settingsFile;
|
|
@@ -18,6 +18,7 @@ export interface GatewayConfig {
|
|
|
18
18
|
readonly baseURL: string;
|
|
19
19
|
readonly apiKeyHelper?: string;
|
|
20
20
|
readonly env: Readonly<Record<string, string>>;
|
|
21
|
+
readonly settings?: Readonly<Record<string, unknown>>;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
// Ids the built-in adapters answer to; a gateway may not take one.
|
|
@@ -34,6 +35,7 @@ const GATEWAY_ENTRY_SCHEMA = z.object({
|
|
|
34
35
|
args: buildOptionalStringArray(),
|
|
35
36
|
apiKeyHelper: buildOptionalNonEmptyString(),
|
|
36
37
|
env: buildStringEnvRecord(),
|
|
38
|
+
settings: buildOptionalSettings(),
|
|
37
39
|
});
|
|
38
40
|
|
|
39
41
|
/**
|
|
@@ -76,12 +78,22 @@ export function collectGateways(
|
|
|
76
78
|
baseURL: parsed.data.baseURL,
|
|
77
79
|
...(parsed.data.apiKeyHelper === undefined ? {} : { apiKeyHelper: parsed.data.apiKeyHelper }),
|
|
78
80
|
env: parsed.data.env,
|
|
81
|
+
...(parsed.data.settings === undefined ? {} : { settings: parsed.data.settings }),
|
|
79
82
|
});
|
|
80
83
|
}
|
|
81
84
|
|
|
82
85
|
return gateways;
|
|
83
86
|
}
|
|
84
87
|
|
|
88
|
+
// Claude Code settings the sessions of this gateway are started with, on top of
|
|
89
|
+
// the ones atc writes itself. Anything but an object is no extra settings.
|
|
90
|
+
function buildOptionalSettings() {
|
|
91
|
+
return z.preprocess(
|
|
92
|
+
(v) => (isRecord(v) ? v : undefined),
|
|
93
|
+
z.record(z.string(), z.unknown()).optional(),
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
85
97
|
function buildOptionalNonEmptyString() {
|
|
86
98
|
return z.preprocess(
|
|
87
99
|
(v) => (typeof v === 'string' && v !== '' ? v : undefined),
|