@amsterdamdatalabs/adl-agent-control-linux-x64 0.1.29 → 0.1.30
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/bin/adl +1 -1
- package/package.json +1 -1
- package/packages/agent-hooks/src/cli/args.ts +2 -2
- package/packages/agent-hooks/src/cli/doctor.ts +2 -1
- package/packages/agent-hooks/src/cli/rules.ts +2 -1
- package/packages/agent-hooks/src/gating/config.ts +11 -0
- package/packages/agent-hooks/src/gating/running-adl.ts +24 -0
- package/packages/agent-hooks/src/hooks/user-prompt-submit/handler.ts +2 -2
- package/packages/agent-hooks/src/main.ts +10 -3
- package/packages/agent-hooks/src/pre-tool-use/handler.ts +5 -4
- package/packages/azdo/src/azdo-wit.ts +8 -2
- package/packages/azdo/src/mcp-server.ts +1 -1
- package/packages/repo-controls/scripts/hooks/pre-push +11 -1
package/bin/adl
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amsterdamdatalabs/adl-agent-control-linux-x64",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.30",
|
|
4
4
|
"description": "adl CLI binary for linux/x64 (agent identity + Azure DevOps front door — see docs/architecture/cli-design.md)",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"os": [
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import { readFileSync, statSync } from 'node:fs'
|
|
13
13
|
import { resolve } from 'node:path'
|
|
14
|
-
import { decodeConfig, type AdlRepoConfig } from '../gating/config.ts'
|
|
14
|
+
import { decodeConfig, describeConfigIssue, type AdlRepoConfig } from '../gating/config.ts'
|
|
15
15
|
import { MARKER, realRootIO, resolveManagedRepo } from '../gating/root.ts'
|
|
16
16
|
import type { HookResult } from '../hooks/contract.ts'
|
|
17
17
|
|
|
@@ -88,7 +88,7 @@ export function loadRepo(cwd: string, repoArg: string | undefined): LoadedRepo |
|
|
|
88
88
|
if (managed.configPath === null) return { ok: true, state, config: defaultConfig() }
|
|
89
89
|
const decoded = decodeConfig(readFileSync(managed.configPath, 'utf8'))
|
|
90
90
|
if (!decoded.ok) {
|
|
91
|
-
return { ok: false, state, error:
|
|
91
|
+
return { ok: false, state, error: describeConfigIssue(managed.configPath, decoded.issue) }
|
|
92
92
|
}
|
|
93
93
|
return { ok: true, state, config: decoded.value }
|
|
94
94
|
}
|
|
@@ -33,6 +33,7 @@ import { scanSkills } from '../hooks/user-prompt-submit/skills.ts'
|
|
|
33
33
|
import { loadTokenizer } from '../hooks/user-prompt-submit/tokenizer.ts'
|
|
34
34
|
import { loadVectorHalf, resolveGlobalArtifactDir } from '../hooks/user-prompt-submit/vector.ts'
|
|
35
35
|
import { HOOKS_DENY_LOG_REL, HOOKS_LOG_REL } from '../hooks/log.ts'
|
|
36
|
+
import { runningAdl } from '../gating/running-adl.ts'
|
|
36
37
|
import { buildEngineContext } from '../rules/engine.ts'
|
|
37
38
|
import { liveEnv, liveRepoIndex } from '../rules/ports.ts'
|
|
38
39
|
import { BUILTIN_RULES, requireRegistered } from '../rules/registry.ts'
|
|
@@ -67,7 +68,7 @@ function repoChecks(loaded: LoadedRepo): { readonly checks: Check[]; readonly co
|
|
|
67
68
|
if (!loaded.ok) return { checks: [repoLine, check('FAIL', 'config', loaded.error)], config: null }
|
|
68
69
|
const root = state.root as string
|
|
69
70
|
const ctx = buildEngineContext(loaded.config.rules, loaded.config.repo, requireRegistered(BUILTIN_RULES), liveRepoIndex(root), liveEnv, undefined, loaded.config.agent)
|
|
70
|
-
if (!ctx.ok) return { checks: [repoLine, check('FAIL', 'config', `${ctx.issue.pointer}: ${ctx.issue.message}`)], config: null }
|
|
71
|
+
if (!ctx.ok) return { checks: [repoLine, check('FAIL', 'config', `${ctx.issue.pointer}: ${ctx.issue.message}${runningAdl()}`)], config: null }
|
|
71
72
|
return { checks: [repoLine, check('ok', 'config', 'valid')], config: loaded.config }
|
|
72
73
|
}
|
|
73
74
|
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import { buildEngineContext } from '../rules/engine.ts'
|
|
15
15
|
import { liveEnv, liveRepoIndex } from '../rules/ports.ts'
|
|
16
|
+
import { runningAdl } from '../gating/running-adl.ts'
|
|
16
17
|
import { BUILTIN_RULES, requireRegistered } from '../rules/registry.ts'
|
|
17
18
|
import type { AnyRuleSpec } from '../rules/spec.ts'
|
|
18
19
|
import { type CliResult, type RepoState, cliError, loadRepo, parseArgs, unmanagedReason } from './args.ts'
|
|
@@ -42,7 +43,7 @@ function resolveRules(cwd: string, repoArg: string | undefined): Resolved | CliR
|
|
|
42
43
|
const { state, config } = loaded
|
|
43
44
|
const registered = requireRegistered(BUILTIN_RULES)
|
|
44
45
|
const ctx = buildEngineContext(config.rules, config.repo, registered, liveRepoIndex(state.root ?? state.start), liveEnv, undefined, config.agent)
|
|
45
|
-
if (!ctx.ok) return cliError(`${state.configPath} ${ctx.issue.pointer}: ${ctx.issue.message}`)
|
|
46
|
+
if (!ctx.ok) return cliError(`${state.configPath} ${ctx.issue.pointer}: ${ctx.issue.message}${runningAdl()}`)
|
|
46
47
|
const hooksEnabled = config.hooks.enabled
|
|
47
48
|
const rules = [...registered.values()].map((spec) => ({
|
|
48
49
|
spec,
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
*/
|
|
63
63
|
|
|
64
64
|
import { MARKER } from './root.ts'
|
|
65
|
+
import { runningAdl } from './running-adl.ts'
|
|
65
66
|
|
|
66
67
|
interface RuleSection {
|
|
67
68
|
readonly enabled: boolean
|
|
@@ -146,6 +147,16 @@ export interface AdlRepoConfig {
|
|
|
146
147
|
|
|
147
148
|
export type ConfigDecodeIssue = { readonly pointer: string; readonly message: string }
|
|
148
149
|
|
|
150
|
+
/**
|
|
151
|
+
* The one place a config-decode failure is rendered for an operator:
|
|
152
|
+
* `invalid <config> at <pointer>: <message> (adl <version> at <exec path>)`
|
|
153
|
+
* (`./running-adl.ts`). Added at rendering only — `ConfigDecodeIssue` stays
|
|
154
|
+
* untouched for programmatic consumers.
|
|
155
|
+
*/
|
|
156
|
+
export function describeConfigIssue(configPath: string, issue: ConfigDecodeIssue): string {
|
|
157
|
+
return `invalid ${configPath} at ${issue.pointer}: ${issue.message}${runningAdl()}`
|
|
158
|
+
}
|
|
159
|
+
|
|
149
160
|
export type ConfigDecodeResult =
|
|
150
161
|
| { readonly ok: true; readonly value: AdlRepoConfig }
|
|
151
162
|
| { readonly ok: false; readonly issue: ConfigDecodeIssue }
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which adl is decoding the config. A stale binary rejecting a newer config's
|
|
3
|
+
* keys ("unknown key(s) 'fleet'") looks identical to a bad config, so every
|
|
4
|
+
* config rejection an operator sees carries `(adl <version> at <exec path>)`.
|
|
5
|
+
*
|
|
6
|
+
* The version belongs to the CLI (`apps/cli/package.json`, what `adl
|
|
7
|
+
* --version` prints). This package must not import upward into the app, so
|
|
8
|
+
* `adl hook` injects it through `run(argv, adlVersion)` in `../main.ts`. A
|
|
9
|
+
* caller that does not inject one (the standalone agent-hooks entry, in-process
|
|
10
|
+
* tests) renders `version unknown` — never a made-up number.
|
|
11
|
+
*
|
|
12
|
+
* The path is `process.execPath`: the compiled binary itself (there
|
|
13
|
+
* `process.argv[1]` is a virtual `/$bunfs` path); under `bun run` it is bun,
|
|
14
|
+
* which is what is actually running.
|
|
15
|
+
*/
|
|
16
|
+
let injectedVersion: string | undefined;
|
|
17
|
+
|
|
18
|
+
export function setRunningAdlVersion(version: string | undefined): void {
|
|
19
|
+
injectedVersion = version;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function runningAdl(): string {
|
|
23
|
+
return ` (adl ${injectedVersion ?? "version unknown"} at ${process.execPath})`;
|
|
24
|
+
}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import { existsSync, readFileSync } from 'node:fs'
|
|
16
16
|
import { join } from 'node:path'
|
|
17
17
|
import type { HookHandler, HookInvocation } from '../contract.ts'
|
|
18
|
-
import { decodeConfig } from '../../gating/config.ts'
|
|
18
|
+
import { decodeConfig, describeConfigIssue } from '../../gating/config.ts'
|
|
19
19
|
import { MARKER } from '../../gating/root.ts'
|
|
20
20
|
import { DEFAULT_SKILLS_DIR } from './constants.ts'
|
|
21
21
|
import { skillRootsFor } from './skill-roots.ts'
|
|
@@ -52,7 +52,7 @@ function resolveSkillRoots(inv: HookInvocation): readonly string[] {
|
|
|
52
52
|
const text = readFileSync(configPath, 'utf8')
|
|
53
53
|
const decoded = decodeConfig(text)
|
|
54
54
|
if (!decoded.ok) {
|
|
55
|
-
throw new Error(
|
|
55
|
+
throw new Error(describeConfigIssue(configPath, decoded.issue))
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
const explicit = decoded.value.hooks.skillsDir
|
|
@@ -72,7 +72,8 @@ import type { HookHandler, HookInvocation, HookResult } from './hooks/contract.t
|
|
|
72
72
|
import { payloadCwd } from './hooks/contract.ts'
|
|
73
73
|
import { resolveHost, readHostFlag, type HostId } from './hosts/spec.ts'
|
|
74
74
|
import { resolveManagedRepo, realRootIO } from './gating/root.ts'
|
|
75
|
-
import {
|
|
75
|
+
import { setRunningAdlVersion } from './gating/running-adl.ts'
|
|
76
|
+
import { decodeConfig, describeConfigIssue, type AdlRepoConfig } from './gating/config.ts'
|
|
76
77
|
import { FAIL_CLOSED_EXIT_CODE } from './rules/engine.ts'
|
|
77
78
|
import { handlePreToolUse } from './pre-tool-use/handler.ts'
|
|
78
79
|
import { handleUserPromptSubmit } from './hooks/user-prompt-submit/handler.ts'
|
|
@@ -123,7 +124,13 @@ function writeResult(result: HookResult): number {
|
|
|
123
124
|
return result.exitCode
|
|
124
125
|
}
|
|
125
126
|
|
|
126
|
-
|
|
127
|
+
/**
|
|
128
|
+
* `adlVersion` is the embedding CLI's version (`adl hook` passes
|
|
129
|
+
* `apps/cli/package.json`'s); the standalone entry passes none, and config
|
|
130
|
+
* errors then say `version unknown` rather than guess.
|
|
131
|
+
*/
|
|
132
|
+
export async function run(argv: readonly string[], adlVersion?: string): Promise<number> {
|
|
133
|
+
setRunningAdlVersion(adlVersion)
|
|
127
134
|
if (argv.includes('--version')) {
|
|
128
135
|
process.stdout.write(`${readPackageVersion()}\n`)
|
|
129
136
|
return 0
|
|
@@ -199,7 +206,7 @@ export async function run(argv: readonly string[]): Promise<number> {
|
|
|
199
206
|
const decoded = decodeConfig(text)
|
|
200
207
|
if (!decoded.ok) {
|
|
201
208
|
process.stderr.write(
|
|
202
|
-
`adl hook:
|
|
209
|
+
`adl hook: ${describeConfigIssue(managed.configPath, decoded.issue)}\n`,
|
|
203
210
|
)
|
|
204
211
|
return 1
|
|
205
212
|
}
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
import { readFileSync } from 'node:fs'
|
|
20
20
|
import { join } from 'node:path'
|
|
21
21
|
import type { ActivityNote, HookInvocation, HookResult } from '../hooks/contract.ts'
|
|
22
|
-
import { decodeConfig } from '../gating/config.ts'
|
|
22
|
+
import { decodeConfig, describeConfigIssue } from '../gating/config.ts'
|
|
23
|
+
import { runningAdl } from '../gating/running-adl.ts'
|
|
23
24
|
import { MARKER } from '../gating/root.ts'
|
|
24
25
|
import {
|
|
25
26
|
buildEngineContext,
|
|
@@ -221,7 +222,7 @@ export async function handlePreToolUse(inv: HookInvocation): Promise<HookResult>
|
|
|
221
222
|
const text = readFileSync(configPath, 'utf8')
|
|
222
223
|
const decoded = decodeConfig(text)
|
|
223
224
|
if (!decoded.ok) {
|
|
224
|
-
throw new Error(`adl hook:
|
|
225
|
+
throw new Error(`adl hook: ${describeConfigIssue(configPath, decoded.issue)}`)
|
|
225
226
|
}
|
|
226
227
|
const config = decoded.value
|
|
227
228
|
|
|
@@ -243,10 +244,10 @@ export async function handlePreToolUse(inv: HookInvocation): Promise<HookResult>
|
|
|
243
244
|
inv.root,
|
|
244
245
|
)
|
|
245
246
|
if (!ctx.ok) {
|
|
246
|
-
inv.record?.({ reason: 'ERROR rules config', message: `adl hook: ${configPath} ${ctx.issue.pointer}: ${ctx.issue.message}` })
|
|
247
|
+
inv.record?.({ reason: 'ERROR rules config', message: `adl hook: ${configPath} ${ctx.issue.pointer}: ${ctx.issue.message}${runningAdl()}` })
|
|
247
248
|
return {
|
|
248
249
|
stdout: '',
|
|
249
|
-
stderr: `adl hook: ${configPath} ${ctx.issue.pointer}: ${ctx.issue.message}\n`,
|
|
250
|
+
stderr: `adl hook: ${configPath} ${ctx.issue.pointer}: ${ctx.issue.message}${runningAdl()}\n`,
|
|
250
251
|
exitCode: FAIL_CLOSED_EXIT_CODE,
|
|
251
252
|
}
|
|
252
253
|
}
|
|
@@ -42,7 +42,12 @@ const isAllowedType = (type: string | undefined): type is AllowedType => (ALLOWE
|
|
|
42
42
|
function assertAllowedType(type: string | undefined): asserts type is AllowedType {
|
|
43
43
|
if (!isAllowedType(type)) throw new Error(`type must be one of ${ALLOWED_TYPES.join(', ')}; got ${type ?? 'undefined'}`);
|
|
44
44
|
}
|
|
45
|
-
|
|
45
|
+
interface WorkItemView {
|
|
46
|
+
id: number; title: string; type: string; state: string; assignedTo: string | undefined; description: string | undefined; url: string | undefined;
|
|
47
|
+
links: { parent: number | null; children: number[]; predecessors: number[]; successors: number[]; related: number[] };
|
|
48
|
+
effort: number | null; failureReason: string | null; fields?: Record<string, unknown>; relations?: unknown[];
|
|
49
|
+
}
|
|
50
|
+
function formatItem(wi: any, raw: boolean): WorkItemView {
|
|
46
51
|
const fields = wi.fields ?? {};
|
|
47
52
|
const relationId = (url: string) => Number(/(\d+)\/?$/.exec(url)?.[1]);
|
|
48
53
|
const links = { parent: null as number | null, children: [] as number[], predecessors: [] as number[], successors: [] as number[], related: [] as number[] };
|
|
@@ -53,7 +58,8 @@ function formatItem(wi: any, raw: boolean): Record<string, unknown> {
|
|
|
53
58
|
if (relation.rel === 'System.LinkTypes.Dependency-Forward') links.successors.push(relationId(relation.url));
|
|
54
59
|
if (relation.rel === 'System.LinkTypes.Related') links.related.push(relationId(relation.url));
|
|
55
60
|
}
|
|
56
|
-
const
|
|
61
|
+
const effort = fields['Microsoft.VSTS.Scheduling.Effort']; const failureReason = fields['Custom.FailureReason'];
|
|
62
|
+
const out: WorkItemView = { id: wi.id, title: fields['System.Title'], type: fields['System.WorkItemType'], state: fields['System.State'], assignedTo: fields['System.AssignedTo']?.displayName, description: fields['System.Description'], url: wi._links?.html?.href, links, effort: typeof effort === 'number' ? effort : null, failureReason: typeof failureReason === 'string' && failureReason ? failureReason : null };
|
|
57
63
|
if (raw) { out.fields = fields; out.relations = wi.relations ?? []; }
|
|
58
64
|
return out;
|
|
59
65
|
}
|
|
@@ -124,7 +124,7 @@ export function createServer(): Server {
|
|
|
124
124
|
},
|
|
125
125
|
{
|
|
126
126
|
name: 'workitem_list_or_get',
|
|
127
|
-
description: 'Get one or many tracked work items, or query the scoped backlog.',
|
|
127
|
+
description: 'Get one or many tracked work items, or query the scoped backlog. Each item carries effort and failureReason (both null when unset).',
|
|
128
128
|
inputSchema: {
|
|
129
129
|
type: 'object',
|
|
130
130
|
properties: {
|
|
@@ -22,8 +22,12 @@ push_updates="$(cat)"
|
|
|
22
22
|
# released on EXIT, which serialised THIS HOOK rather than the network push,
|
|
23
23
|
# so the window it claimed to close stayed open the whole time.
|
|
24
24
|
PUSH_TREE=""
|
|
25
|
+
PUSH_ARCHIVE=""
|
|
25
26
|
cleanup_pre_push() {
|
|
26
27
|
local status=$?
|
|
28
|
+
if [[ -n "${PUSH_ARCHIVE}" && -e "${PUSH_ARCHIVE}" ]]; then
|
|
29
|
+
rm -f -- "${PUSH_ARCHIVE}"
|
|
30
|
+
fi
|
|
27
31
|
if [[ -n "${PUSH_TREE}" && -d "${PUSH_TREE}" ]]; then
|
|
28
32
|
rm -rf -- "${PUSH_TREE}"
|
|
29
33
|
fi
|
|
@@ -109,7 +113,13 @@ if [[ -f "$STATIC_CACHE" ]]; then
|
|
|
109
113
|
else
|
|
110
114
|
echo 'pre-push: exporting HEAD tree for static checks'
|
|
111
115
|
PUSH_TREE="$(mktemp -d "${TMPDIR:-/tmp}/adl-pre-push-head.XXXXXX")"
|
|
112
|
-
git archive HEAD | tar -x
|
|
116
|
+
# Not `git archive HEAD | tar -x`: on the macOS CI agent that pipe failed
|
|
117
|
+
# pushes with 141 (SIGPIPE under pipefail) -- a tar that stops reading before
|
|
118
|
+
# git archive finishes writing kills the writer. A file has no reader to lose.
|
|
119
|
+
PUSH_ARCHIVE="${PUSH_TREE}.tar"
|
|
120
|
+
git archive --format=tar -o "$PUSH_ARCHIVE" HEAD
|
|
121
|
+
tar -xf "$PUSH_ARCHIVE" -C "$PUSH_TREE"
|
|
122
|
+
rm -f -- "$PUSH_ARCHIVE"
|
|
113
123
|
# Untracked paths the export needs, declared by the repository. This was
|
|
114
124
|
# `ln -s "$ROOT/node_modules"` -- a package that calls itself language
|
|
115
125
|
# agnostic assuming JavaScript. A Rust repo links `target/`, a Python one its
|