@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 CHANGED
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/bin/adl
3
3
  size: 82912736 bytes
4
- sha256: 2b01ff7bfab3461ee9bc678cd6c7660691505b0bebd617f922de743b0187e197
4
+ sha256: 54d5ec51e9782dda1e62b53d0b327b29028088a68fb4eed8f27cc195d53819c5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amsterdamdatalabs/adl-agent-control-linux-x64",
3
- "version": "0.1.29",
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: `invalid ${managed.configPath} at ${decoded.issue.pointer}: ${decoded.issue.message}` }
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(`invalid ${configPath} at ${decoded.issue.pointer}: ${decoded.issue.message}`)
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 { decodeConfig, type AdlRepoConfig } from './gating/config.ts'
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
- export async function run(argv: readonly string[]): Promise<number> {
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: invalid ${managed.configPath} at ${decoded.issue.pointer}: ${decoded.issue.message}\n`,
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: invalid ${configPath} at ${decoded.issue.pointer}: ${decoded.issue.message}`)
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
- function formatItem(wi: any, raw: boolean): Record<string, unknown> {
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 out: Record<string, unknown> = { 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, customFields: Object.fromEntries(Object.entries(fields).filter(([name]) => name.startsWith('Custom.'))) };
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 -C "$PUSH_TREE"
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