agency-lang 0.14.0 → 0.15.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 (62) hide show
  1. package/dist/lib/cli/local.d.ts +14 -3
  2. package/dist/lib/cli/local.js +62 -14
  3. package/dist/lib/cli/localFlag.d.ts +12 -0
  4. package/dist/lib/cli/localFlag.js +16 -0
  5. package/dist/lib/runtime/interrupts.js +2 -0
  6. package/dist/lib/runtime/localProvider.d.ts +76 -0
  7. package/dist/lib/runtime/localProvider.js +127 -0
  8. package/dist/lib/runtime/node.js +2 -0
  9. package/dist/lib/statelogClient.d.ts +17 -0
  10. package/dist/lib/statelogClient.js +17 -0
  11. package/dist/lib/stdlib/localModelManifest.d.ts +14 -0
  12. package/dist/lib/stdlib/localModelManifest.js +56 -0
  13. package/dist/lib/stdlib/localModels.d.ts +26 -24
  14. package/dist/lib/stdlib/localModels.js +81 -112
  15. package/dist/lib/stdlib/mcpBridge.mjs +3 -2
  16. package/dist/lib/stdlib/mcpResolver.d.ts +1 -1
  17. package/dist/lib/stdlib/mcpResolver.js +2 -2
  18. package/dist/scripts/agency.js +26 -8
  19. package/package.json +2 -2
  20. package/stdlib/agents/data.agency +1 -1
  21. package/stdlib/agents/data.js +1 -1
  22. package/stdlib/capabilities.agency +5 -4
  23. package/stdlib/capabilities.js +2 -2
  24. package/stdlib/data/connector.agency +66 -0
  25. package/stdlib/data/connector.js +976 -0
  26. package/stdlib/data/finance/dbnomics.agency +5 -4
  27. package/stdlib/data/finance/dbnomics.js +17 -20
  28. package/stdlib/data/finance/edgar.agency +8 -7
  29. package/stdlib/data/finance/edgar.js +58 -73
  30. package/stdlib/data/finance/fred.agency +6 -5
  31. package/stdlib/data/finance/fred.js +31 -37
  32. package/stdlib/data/{finance → news}/gdelt.agency +5 -5
  33. package/stdlib/data/{finance → news}/gdelt.js +48 -63
  34. package/stdlib/data/people/littlesis.agency +6 -20
  35. package/stdlib/data/people/littlesis.js +75 -246
  36. package/stdlib/data/social/bluesky.agency +291 -0
  37. package/stdlib/data/social/bluesky.js +2974 -0
  38. package/stdlib/data/tech/hackernews.agency +12 -42
  39. package/stdlib/data/tech/hackernews.js +115 -591
  40. package/stdlib/data/tech/yc.agency +6 -20
  41. package/stdlib/data/tech/yc.js +102 -320
  42. package/stdlib/data/usaspending.agency +3 -17
  43. package/stdlib/data/usaspending.js +41 -205
  44. package/stdlib/data/wikidata.agency +4 -18
  45. package/stdlib/data/wikidata.js +66 -245
  46. package/stdlib/docs/cli/local.md +14 -11
  47. package/stdlib/docs/guide/using-local-models.md +188 -0
  48. package/stdlib/docs/stdlib/capabilities.md +17 -15
  49. package/stdlib/docs/stdlib/data/connector.md +139 -0
  50. package/stdlib/docs/stdlib/data/finance/dbnomics.md +2 -2
  51. package/stdlib/docs/stdlib/data/finance/edgar.md +3 -3
  52. package/stdlib/docs/stdlib/data/finance/fred.md +5 -5
  53. package/stdlib/docs/stdlib/data/{finance → news}/gdelt.md +4 -4
  54. package/stdlib/docs/stdlib/data/people/littlesis.md +5 -5
  55. package/stdlib/docs/stdlib/data/social/bluesky.md +206 -0
  56. package/stdlib/docs/stdlib/data/tech/hackernews.md +4 -4
  57. package/stdlib/docs/stdlib/data/tech/yc.md +5 -5
  58. package/stdlib/docs/stdlib/data/usaspending.md +2 -2
  59. package/stdlib/docs/stdlib/data/wikidata.md +3 -3
  60. package/dist/lib/stdlib/providers/llama-cpp.mjs +0 -79
  61. package/dist/lib/stdlib/providers/llamaModelConfig.d.ts +0 -7
  62. package/dist/lib/stdlib/providers/llamaModelConfig.js +0 -7
@@ -1,9 +1,20 @@
1
- import { type RefreshResult } from "../stdlib/localModels.js";
2
- export declare function aliasList(file?: string): import("../stdlib/localModels.js").ModelNameEntry[];
1
+ import { type ModelNameEntry, type RefreshResult } from "../stdlib/localModels.js";
2
+ export declare function aliasList(file?: string): ModelNameEntry[];
3
3
  export declare function aliasAdd(name: string, uri: string, file?: string): string;
4
4
  export declare function aliasRemove(name: string, file?: string): string;
5
+ /** Deliberately ungated: browsing the catalog needs no provider package
6
+ * (only download/remove do), and the pre-install experience — see what is
7
+ * available, then get told what to install — is the point. */
5
8
  export declare function runList(): void;
6
- export declare function runDownload(value: string): Promise<void>;
9
+ export declare const CUSTOM_CHOICE = "__custom__";
10
+ /** Picker rows for the no-argument `agency local download`. Metadata-less
11
+ * aliases get a bare name; the trailing choice lets the user type an hf: URI
12
+ * or .gguf path. */
13
+ export declare function downloadChoices(entries: ModelNameEntry[]): {
14
+ title: string;
15
+ value: string;
16
+ }[];
17
+ export declare function runDownload(value?: string): Promise<void>;
7
18
  export declare function runRemove(name: string): void;
8
19
  export declare function runResolve(value: string): void;
9
20
  export declare function runAliasList(): void;
@@ -1,4 +1,6 @@
1
- import { _resolveModelName, _downloadModel, _listDownloadedModels, _listModelNames, _aliasModel, _unaliasModel, _removeModel, hasLocalModelSupport, formatGB, formatModelCatalog, _refreshCatalog, } from "../stdlib/localModels.js";
1
+ import prompts from "prompts";
2
+ import { _resolveModelName, _downloadModel, _listDownloadedModels, _listModelNames, _modelsCacheDir, _aliasModel, _unaliasModel, _removeModel, hasLocalModelSupport, formatGB, formatModelCatalog, formatLocalList, _refreshCatalog, } from "../stdlib/localModels.js";
3
+ import { readDownloadManifest } from "../stdlib/localModelManifest.js";
2
4
  import { ttyColor } from "../utils/termcolors.js";
3
5
  /** Install-gate for I/O commands. Honors the AGENCY_LLAMA_PROVIDER_MODULE
4
6
  * override the same way `requireSupport()` in `localModels.ts` does — a
@@ -31,26 +33,72 @@ export function aliasRemove(name, file) {
31
33
  }
32
34
  return inspected;
33
35
  }
36
+ /** Deliberately ungated: browsing the catalog needs no provider package
37
+ * (only download/remove do), and the pre-install experience — see what is
38
+ * available, then get told what to install — is the point. */
34
39
  export function runList() {
35
- gate();
36
- const models = _listDownloadedModels();
37
- if (models.length === 0) {
38
- console.log("No models downloaded.");
39
- return;
40
- }
41
- for (const m of models) {
42
- console.log(`${m.name}\t${formatGB(m.sizeBytes)}`);
43
- }
44
- const total = models.reduce((sum, m) => sum + m.sizeBytes, 0);
45
- console.log(`Total: ${formatGB(total)}`);
40
+ const dir = _modelsCacheDir();
41
+ console.log(formatLocalList({
42
+ dir,
43
+ entries: _listModelNames(),
44
+ manifest: readDownloadManifest(dir),
45
+ files: _listDownloadedModels(),
46
+ }));
47
+ }
48
+ export const CUSTOM_CHOICE = "__custom__";
49
+ /** Picker rows for the no-argument `agency local download`. Metadata-less
50
+ * aliases get a bare name; the trailing choice lets the user type an hf: URI
51
+ * or .gguf path. */
52
+ export function downloadChoices(entries) {
53
+ const rows = entries.map((e) => ({
54
+ title: e.params !== undefined && e.sizeBytes !== undefined
55
+ ? `${e.name} (${e.params}, ${formatGB(e.sizeBytes)})`
56
+ : e.name,
57
+ value: e.name,
58
+ }));
59
+ return [...rows, { title: "custom (hf: URI or .gguf path)…", value: CUSTOM_CHOICE }];
46
60
  }
47
61
  export async function runDownload(value) {
48
62
  gate();
63
+ let picked = value;
64
+ if (picked === undefined) {
65
+ // Prompting needs BOTH ends of the terminal: a TTY stdout to draw on and
66
+ // a TTY stdin to read from (`agency local download < /dev/null` from a
67
+ // terminal has a TTY stdout but nothing to read).
68
+ if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
69
+ // A script that reaches this point asked for a download and did not
70
+ // get one — print what is available and fail.
71
+ console.log(formatModelCatalog());
72
+ console.error("Pass a model: agency local download <name>");
73
+ process.exit(1);
74
+ }
75
+ const answer = await prompts({
76
+ type: "select",
77
+ name: "model",
78
+ message: "Which model do you want to download?",
79
+ choices: downloadChoices(_listModelNames()),
80
+ });
81
+ // Cancellation can surface as a missing key or as null — treat both as
82
+ // "exit 0, nothing downloaded".
83
+ if (answer.model == null)
84
+ return;
85
+ picked = answer.model;
86
+ if (picked === CUSTOM_CHOICE) {
87
+ const custom = await prompts({
88
+ type: "text",
89
+ name: "value",
90
+ message: "hf: URI or .gguf path:",
91
+ });
92
+ if (custom.value == null || custom.value === "")
93
+ return;
94
+ picked = custom.value;
95
+ }
96
+ }
49
97
  // Show the source it resolved to (the hf: URI for a name/alias) and the
50
98
  // local path it landed at. For a .gguf-path input the two are the same, so
51
99
  // the source line is skipped.
52
- const source = _resolveModelName(value);
53
- const modelPath = await _downloadModel(value);
100
+ const source = _resolveModelName(picked);
101
+ const modelPath = await _downloadModel(picked);
54
102
  if (source !== modelPath) {
55
103
  console.log(`source: ${source}`);
56
104
  }
@@ -0,0 +1,12 @@
1
+ import type { ResolvedModelFlag } from "../config.js";
2
+ /** Turn `agency run --local <value>` into the shared model-flag shape:
3
+ * resolve the name (curated / alias / hf: URI / .gguf path), download and
4
+ * verify if needed (progress prints here, in the parent, before the program
5
+ * starts), and pin the llama-cpp provider. Errors (package missing, unknown
6
+ * name, failed download) carry user-ready messages from localModels.
7
+ *
8
+ * The path is absolutized before it is baked into config: LlamaCPP rejects a
9
+ * bare separator-less filename (ambiguous with a model name), which is what
10
+ * a user-supplied `--local model.gguf` would otherwise arrive as, and an
11
+ * absolute path also keeps the child process independent of cwd drift. */
12
+ export declare function resolveLocalRunFlag(value: string): Promise<ResolvedModelFlag>;
@@ -0,0 +1,16 @@
1
+ import * as path from "node:path";
2
+ import { _registerLocalModel } from "../stdlib/localModels.js";
3
+ /** Turn `agency run --local <value>` into the shared model-flag shape:
4
+ * resolve the name (curated / alias / hf: URI / .gguf path), download and
5
+ * verify if needed (progress prints here, in the parent, before the program
6
+ * starts), and pin the llama-cpp provider. Errors (package missing, unknown
7
+ * name, failed download) carry user-ready messages from localModels.
8
+ *
9
+ * The path is absolutized before it is baked into config: LlamaCPP rejects a
10
+ * bare separator-less filename (ambiguous with a model name), which is what
11
+ * a user-supplied `--local model.gguf` would otherwise arrive as, and an
12
+ * absolute path also keeps the child process independent of cwd drift. */
13
+ export async function resolveLocalRunFlag(value) {
14
+ const modelPath = await _registerLocalModel(value);
15
+ return { model: path.resolve(modelPath), explicitProvider: "llama-cpp" };
16
+ }
@@ -11,6 +11,7 @@ import { isAborted } from "./abortedResult.js";
11
11
  import { mergeFor, mergeForIpc } from "./effectMerge.js";
12
12
  import { applyOverrides } from "./rewind.js";
13
13
  import { loadProviderModules } from "./providerModules.js";
14
+ import { ensureConfiguredLocalProvider } from "./localProvider.js";
14
15
  import { installRunPolicyHandler } from "./runPolicyHandler.js";
15
16
  import { unwrapServedInvocationOutcome, } from "./invocationUsage.js";
16
17
  import { finishServedInvocation } from "./servedInvocationLifecycle.js";
@@ -613,6 +614,7 @@ async function respondToInterruptsCore(args) {
613
614
  // is process-global, not part of serialized checkpoint state), so re-register
614
615
  // before resuming. Idempotent in-process via loadProviderModules' guard.
615
616
  await loadProviderModules(execCtx);
617
+ await ensureConfiguredLocalProvider(execCtx);
616
618
  // This is the first restore on this execCtx — record it as such.
617
619
  execCtx._restoreCount++;
618
620
  execCtx.statelogClient.checkpointRestored({
@@ -0,0 +1,76 @@
1
+ import { type LlamaCppModule } from "smoltalk";
2
+ /** Discover global `node_modules` roots reported by `npm` and `pnpm`, in that
3
+ * order. Each entry is the directory printed by `<tool> root -g` (which is
4
+ * itself a `node_modules` dir, e.g. `/opt/homebrew/lib/node_modules` for
5
+ * Homebrew npm, `~/Library/pnpm/global/5/node_modules` for pnpm). Failures
6
+ * (tool not installed, exit non-zero, dir missing) are silently skipped. */
7
+ export declare function globalNodeModulesRoots(): string[];
8
+ /** Try to resolve `smoltalk-llama-cpp` from the given global `node_modules`
9
+ * roots. Each `root` is itself a `node_modules` directory (the convention
10
+ * `npm root -g` / `pnpm root -g` uses). Node's resolver looks for
11
+ * `<parent>/node_modules/<pkg>` for each parent dir it walks up, so the
12
+ * createRequire base must live in the root's PARENT directory — from
13
+ * `<root>/../_resolver.js` it correctly finds `<root>/smoltalk-llama-cpp/...`.
14
+ * Exported for unit-testing with a controllable list of roots. */
15
+ export declare function resolveSmoltalkLlamaCppFromRoots(roots: string[]): string | null;
16
+ /** Local require paths first, then the global npm/pnpm roots. Used by the
17
+ * install gate (`hasLocalModelSupport`), which only cares whether the
18
+ * package is reachable at all. */
19
+ export declare function resolveSmoltalkLlamaCppEntry(): string | null;
20
+ /** The chooseEntryPath decision: what to hand smoltalk, and how the package
21
+ * was found (for the `localModelLoaded` statelog event). `entryPath` is
22
+ * undefined for a bare import — sources "local" and "none". */
23
+ export type LlamaCppEntryChoice = {
24
+ entryPath: string | undefined;
25
+ source: "override" | "local" | "global" | "none";
26
+ };
27
+ /** What to hand smoltalk's loadLlamaCpp: an explicit entry path, or
28
+ * undefined for a bare import. Pure — the probes come in as values/thunks so
29
+ * tests pin each branch. Precedence:
30
+ * 1. AGENCY_LLAMA_PROVIDER_MODULE (the test/advanced escape hatch; an ENTRY
31
+ * PATH to a plugin-shaped module — a LlamaCPP class + resolveModel),
32
+ * absolutized against cwd.
33
+ * 2. Locally resolvable → undefined: smoltalk imports the bare specifier
34
+ * itself (under pnpm its peer instancing picks the right copy).
35
+ * 3. A global-roots hit → that path (smoltalk cannot see global installs).
36
+ * 4. Nothing → undefined: smoltalk's import fails with its install hint. */
37
+ export declare function chooseEntryPath(args: {
38
+ override: string | undefined;
39
+ cwd: string;
40
+ localEntry: string | null;
41
+ globalEntry: () => string | null;
42
+ }): LlamaCppEntryChoice;
43
+ /** The one field of StatelogClient this module needs. Structural so the
44
+ * runtime bootstrap can pass its client without this file importing the
45
+ * statelog module. */
46
+ type LocalModelEventSink = {
47
+ localModelLoaded(args: {
48
+ model?: string;
49
+ entryPath?: string;
50
+ entrySource: string;
51
+ }): Promise<void>;
52
+ };
53
+ /** Bootstrap hook: when the baked/overridden config routes calls to the
54
+ * llama-cpp provider, load it eagerly with the probe-assisted entry path.
55
+ * smoltalk auto-loads on text() for resolvable installs; this covers the
56
+ * global-CLI + global-plugin layout its bare import cannot see. Emits a
57
+ * `localModelLoaded` statelog event naming the pinned model and where the
58
+ * provider package came from. */
59
+ export declare function ensureConfiguredLocalProvider(execCtx: {
60
+ smoltalkDefaults?: {
61
+ provider?: string;
62
+ model?: string;
63
+ };
64
+ statelogClient?: LocalModelEventSink;
65
+ }): Promise<void>;
66
+ /** Like loadLocalProvider, also returning HOW the package was found, for
67
+ * callers that emit the `localModelLoaded` event. */
68
+ export declare function loadLocalProviderDetailed(): Promise<{
69
+ module: LlamaCppModule;
70
+ choice: LlamaCppEntryChoice;
71
+ }>;
72
+ /** Load smoltalk's optional llama-cpp provider, resolving the package for
73
+ * layouts smoltalk cannot see on its own (see chooseEntryPath). Caching,
74
+ * idempotency, and registration live in smoltalk's loader, not here. */
75
+ export declare function loadLocalProvider(): Promise<LlamaCppModule>;
76
+ export {};
@@ -0,0 +1,127 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { createRequire } from "node:module";
4
+ import { execFileSync } from "node:child_process";
5
+ import { loadLlamaCpp } from "smoltalk";
6
+ // Cached so we don't shell out repeatedly per process.
7
+ let cachedGlobalRoots = null;
8
+ /** Discover global `node_modules` roots reported by `npm` and `pnpm`, in that
9
+ * order. Each entry is the directory printed by `<tool> root -g` (which is
10
+ * itself a `node_modules` dir, e.g. `/opt/homebrew/lib/node_modules` for
11
+ * Homebrew npm, `~/Library/pnpm/global/5/node_modules` for pnpm). Failures
12
+ * (tool not installed, exit non-zero, dir missing) are silently skipped. */
13
+ export function globalNodeModulesRoots() {
14
+ if (cachedGlobalRoots !== null) {
15
+ return cachedGlobalRoots;
16
+ }
17
+ const roots = [];
18
+ for (const cmd of ["npm", "pnpm"]) {
19
+ try {
20
+ const out = execFileSync(cmd, ["root", "-g"], {
21
+ encoding: "utf-8",
22
+ stdio: ["ignore", "pipe", "ignore"],
23
+ }).trim();
24
+ if (out && fs.existsSync(out) && !roots.includes(out)) {
25
+ roots.push(out);
26
+ }
27
+ }
28
+ catch {
29
+ /* tool not installed or failed — skip */
30
+ }
31
+ }
32
+ cachedGlobalRoots = roots;
33
+ return roots;
34
+ }
35
+ /** Try to resolve `smoltalk-llama-cpp` from the given global `node_modules`
36
+ * roots. Each `root` is itself a `node_modules` directory (the convention
37
+ * `npm root -g` / `pnpm root -g` uses). Node's resolver looks for
38
+ * `<parent>/node_modules/<pkg>` for each parent dir it walks up, so the
39
+ * createRequire base must live in the root's PARENT directory — from
40
+ * `<root>/../_resolver.js` it correctly finds `<root>/smoltalk-llama-cpp/...`.
41
+ * Exported for unit-testing with a controllable list of roots. */
42
+ export function resolveSmoltalkLlamaCppFromRoots(roots) {
43
+ for (const root of roots) {
44
+ try {
45
+ const req = createRequire(path.join(root, "..", "_resolver.js"));
46
+ return req.resolve("smoltalk-llama-cpp");
47
+ }
48
+ catch {
49
+ /* not in this root — try the next */
50
+ }
51
+ }
52
+ return null;
53
+ }
54
+ /** The package entry resolvable from agency's own require paths (in-workspace
55
+ * and user-project installs). */
56
+ function localEntry() {
57
+ try {
58
+ return createRequire(import.meta.url).resolve("smoltalk-llama-cpp");
59
+ }
60
+ catch {
61
+ return null;
62
+ }
63
+ }
64
+ /** Local require paths first, then the global npm/pnpm roots. Used by the
65
+ * install gate (`hasLocalModelSupport`), which only cares whether the
66
+ * package is reachable at all. */
67
+ export function resolveSmoltalkLlamaCppEntry() {
68
+ return localEntry() ?? resolveSmoltalkLlamaCppFromRoots(globalNodeModulesRoots());
69
+ }
70
+ /** What to hand smoltalk's loadLlamaCpp: an explicit entry path, or
71
+ * undefined for a bare import. Pure — the probes come in as values/thunks so
72
+ * tests pin each branch. Precedence:
73
+ * 1. AGENCY_LLAMA_PROVIDER_MODULE (the test/advanced escape hatch; an ENTRY
74
+ * PATH to a plugin-shaped module — a LlamaCPP class + resolveModel),
75
+ * absolutized against cwd.
76
+ * 2. Locally resolvable → undefined: smoltalk imports the bare specifier
77
+ * itself (under pnpm its peer instancing picks the right copy).
78
+ * 3. A global-roots hit → that path (smoltalk cannot see global installs).
79
+ * 4. Nothing → undefined: smoltalk's import fails with its install hint. */
80
+ export function chooseEntryPath(args) {
81
+ if (args.override !== undefined && args.override !== "") {
82
+ const abs = path.isAbsolute(args.override)
83
+ ? args.override
84
+ : path.resolve(args.cwd, args.override);
85
+ return { entryPath: abs, source: "override" };
86
+ }
87
+ if (args.localEntry !== null)
88
+ return { entryPath: undefined, source: "local" };
89
+ const globalPath = args.globalEntry();
90
+ if (globalPath !== null)
91
+ return { entryPath: globalPath, source: "global" };
92
+ return { entryPath: undefined, source: "none" };
93
+ }
94
+ /** Bootstrap hook: when the baked/overridden config routes calls to the
95
+ * llama-cpp provider, load it eagerly with the probe-assisted entry path.
96
+ * smoltalk auto-loads on text() for resolvable installs; this covers the
97
+ * global-CLI + global-plugin layout its bare import cannot see. Emits a
98
+ * `localModelLoaded` statelog event naming the pinned model and where the
99
+ * provider package came from. */
100
+ export async function ensureConfiguredLocalProvider(execCtx) {
101
+ if (execCtx.smoltalkDefaults?.provider !== "llama-cpp")
102
+ return;
103
+ const { choice } = await loadLocalProviderDetailed();
104
+ void execCtx.statelogClient?.localModelLoaded({
105
+ model: execCtx.smoltalkDefaults?.model,
106
+ entryPath: choice.entryPath,
107
+ entrySource: choice.source,
108
+ });
109
+ }
110
+ /** Like loadLocalProvider, also returning HOW the package was found, for
111
+ * callers that emit the `localModelLoaded` event. */
112
+ export async function loadLocalProviderDetailed() {
113
+ const choice = chooseEntryPath({
114
+ override: process.env.AGENCY_LLAMA_PROVIDER_MODULE,
115
+ cwd: process.cwd(),
116
+ localEntry: localEntry(),
117
+ globalEntry: () => resolveSmoltalkLlamaCppFromRoots(globalNodeModulesRoots()),
118
+ });
119
+ const module = await loadLlamaCpp(choice.entryPath === undefined ? undefined : { entryPath: choice.entryPath });
120
+ return { module, choice };
121
+ }
122
+ /** Load smoltalk's optional llama-cpp provider, resolving the package for
123
+ * layouts smoltalk cannot see on its own (see chooseEntryPath). Caching,
124
+ * idempotency, and registration live in smoltalk's loader, not here. */
125
+ export async function loadLocalProvider() {
126
+ return (await loadLocalProviderDetailed()).module;
127
+ }
@@ -6,6 +6,7 @@ import { AgencyCancelledError, CheckpointError, RestoreSignal, } from "./errors.
6
6
  import { ThreadStore } from "./state/threadStore.js";
7
7
  import { __initAllRegistered, __initAllRegisteredCallbacks } from "./crossModuleInitRegistry.js";
8
8
  import { loadProviderModules } from "./providerModules.js";
9
+ import { ensureConfiguredLocalProvider } from "./localProvider.js";
9
10
  import { resolveTraceFilePath } from "./trace/traceWriter.js";
10
11
  import { getSubprocessRunInfo } from "./subprocessRunInfo.js";
11
12
  import { resolveInvocation } from "./invocationOptions.js";
@@ -146,6 +147,7 @@ async function initFreshExecCtx(execCtx, opts) {
146
147
  // Process-global + idempotent (see loadProviderModules), so it is safe and
147
148
  // cheap to call on every fresh run.
148
149
  await loadProviderModules(execCtx);
150
+ await ensureConfiguredLocalProvider(execCtx);
149
151
  await runInBootstrapFrame(execCtx, () => __initAllRegistered(execCtx));
150
152
  if (initializeGlobals) {
151
153
  await runInBootstrapFrame(execCtx, () => initializeGlobals(execCtx));
@@ -266,6 +266,23 @@ export declare class StatelogClient {
266
266
  timeTaken?: number;
267
267
  cost?: TokenCost;
268
268
  }): Promise<void>;
269
+ /**
270
+ * Emit a `localModelLoaded` leaf event: which local model the run is
271
+ * pinned to and where the llama-cpp provider package was resolved from.
272
+ * `entrySource` is the chooseEntryPath decision — "override"
273
+ * (AGENCY_LLAMA_PROVIDER_MODULE), "local" (bare import from the local
274
+ * require paths), or "global" (a global npm/pnpm install root). Emitted
275
+ * once per process at bootstrap when the baked config names the llama-cpp
276
+ * provider, and when agency code registers a local model explicitly.
277
+ */
278
+ localModelLoaded({ model, entryPath, entrySource, }: {
279
+ /** The .gguf path (or model name) calls are pinned to, when known. */
280
+ model?: string;
281
+ /** Resolved entry file of the provider package, when not a bare import. */
282
+ entryPath?: string;
283
+ /** How the provider package was found (see chooseEntryPath). */
284
+ entrySource: string;
285
+ }): Promise<void>;
269
286
  /**
270
287
  * Memory umbrella-span marker events.
271
288
  *
@@ -433,6 +433,23 @@ export class StatelogClient {
433
433
  cost,
434
434
  });
435
435
  }
436
+ /**
437
+ * Emit a `localModelLoaded` leaf event: which local model the run is
438
+ * pinned to and where the llama-cpp provider package was resolved from.
439
+ * `entrySource` is the chooseEntryPath decision — "override"
440
+ * (AGENCY_LLAMA_PROVIDER_MODULE), "local" (bare import from the local
441
+ * require paths), or "global" (a global npm/pnpm install root). Emitted
442
+ * once per process at bootstrap when the baked config names the llama-cpp
443
+ * provider, and when agency code registers a local model explicitly.
444
+ */
445
+ async localModelLoaded({ model, entryPath, entrySource, }) {
446
+ await this.post({
447
+ type: "localModelLoaded",
448
+ model,
449
+ entryPath,
450
+ entrySource,
451
+ });
452
+ }
436
453
  /**
437
454
  * Memory umbrella-span marker events.
438
455
  *
@@ -0,0 +1,14 @@
1
+ /** downloads.json in the models cache dir: resolved model URI → the .gguf
2
+ * basename node-llama-cpp stored it under. Written on successful (verified)
3
+ * download; read only by the `agency local list` view. Display metadata:
4
+ * resolution, downloading, and verification never consult it, so a missing
5
+ * or corrupt manifest can mislabel the list and nothing else — and for the
6
+ * same reason `recordDownload` NEVER throws (a bookkeeping failure must not
7
+ * turn a successful download into a failed command). Writes go through a
8
+ * uniquely-named sibling temp file + rename: an interrupted write keeps the
9
+ * previous valid manifest, and two concurrent downloaders cannot collide on
10
+ * the temp file — they race whole-file on the rename (last writer wins),
11
+ * accepted for display metadata. */
12
+ export declare const MANIFEST_FILE = "downloads.json";
13
+ export declare function readDownloadManifest(dir: string): Record<string, string>;
14
+ export declare function recordDownload(dir: string, uri: string, file: string): void;
@@ -0,0 +1,56 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import * as crypto from "node:crypto";
4
+ /** downloads.json in the models cache dir: resolved model URI → the .gguf
5
+ * basename node-llama-cpp stored it under. Written on successful (verified)
6
+ * download; read only by the `agency local list` view. Display metadata:
7
+ * resolution, downloading, and verification never consult it, so a missing
8
+ * or corrupt manifest can mislabel the list and nothing else — and for the
9
+ * same reason `recordDownload` NEVER throws (a bookkeeping failure must not
10
+ * turn a successful download into a failed command). Writes go through a
11
+ * uniquely-named sibling temp file + rename: an interrupted write keeps the
12
+ * previous valid manifest, and two concurrent downloaders cannot collide on
13
+ * the temp file — they race whole-file on the rename (last writer wins),
14
+ * accepted for display metadata. */
15
+ export const MANIFEST_FILE = "downloads.json";
16
+ export function readDownloadManifest(dir) {
17
+ try {
18
+ const parsed = JSON.parse(fs.readFileSync(path.join(dir, MANIFEST_FILE), "utf-8"));
19
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
20
+ return {};
21
+ }
22
+ // Null-prototype: on-disk JSON is untrusted, and keys like "__proto__" /
23
+ // "toString" must behave as plain data (same convention as smoltalk's
24
+ // provider registry).
25
+ const out = Object.create(null);
26
+ for (const [k, v] of Object.entries(parsed)) {
27
+ if (typeof v === "string")
28
+ out[k] = v;
29
+ }
30
+ return out;
31
+ }
32
+ catch {
33
+ return {};
34
+ }
35
+ }
36
+ export function recordDownload(dir, uri, file) {
37
+ // Unique per writer: a fixed name would make two concurrent downloads race
38
+ // on the SAME temp file — the loser's rename throws ENOENT after its model
39
+ // downloaded fine.
40
+ const tmp = path.join(dir, `${MANIFEST_FILE}.${process.pid}-${crypto.randomBytes(4).toString("hex")}.tmp`);
41
+ try {
42
+ fs.mkdirSync(dir, { recursive: true });
43
+ const next = { ...readDownloadManifest(dir), [uri]: file };
44
+ fs.writeFileSync(tmp, JSON.stringify(next, null, 2) + "\n");
45
+ fs.renameSync(tmp, path.join(dir, MANIFEST_FILE));
46
+ }
47
+ catch (err) {
48
+ try {
49
+ fs.unlinkSync(tmp);
50
+ }
51
+ catch {
52
+ /* never written, or already renamed */
53
+ }
54
+ console.warn(`Could not record the download in ${MANIFEST_FILE} (the list view may not mark this model):`, err.message);
55
+ }
56
+ }
@@ -35,6 +35,10 @@ export declare const CURATED_LOCAL_MODELS: Record<string, ModelInfo>;
35
35
  * `agency local download` pre-populates what `agency agent --local-model`
36
36
  * reuses). */
37
37
  export declare function defaultCacheDir(): string;
38
+ /** The resolved models cache dir (AGENCY_MODELS_DIR env → agency.json
39
+ * client.modelsDir → ~/.agency-agent/models). Exported so `agency local
40
+ * list` can name where downloads land. */
41
+ export declare function _modelsCacheDir(cacheDir?: string): string;
38
42
  /** The agency.json that owns aliases: nearest `agency.json` walking up from
39
43
  * `startDir` (cwd by default); falls back to `~/agency.json` when none is
40
44
  * found. Exported so the CLI can echo it on every write. */
@@ -139,29 +143,6 @@ export declare function _refreshCatalog(opts?: {
139
143
  fetcher?: (url: string) => Promise<string>;
140
144
  file?: string;
141
145
  }): Promise<RefreshResult>;
142
- /** Discover global `node_modules` roots reported by `npm` and `pnpm`, in that
143
- * order. Each entry is the directory printed by `<tool> root -g` (which is
144
- * itself a `node_modules` dir, e.g. `/opt/homebrew/lib/node_modules` for
145
- * Homebrew npm, `~/Library/pnpm/global/5/node_modules` for pnpm). Failures
146
- * (tool not installed, exit non-zero, dir missing) are silently skipped. */
147
- export declare function globalNodeModulesRoots(): string[];
148
- /** Try to resolve `smoltalk-llama-cpp` from the given global `node_modules`
149
- * roots. Each `root` is itself a `node_modules` directory (the convention
150
- * `npm root -g` / `pnpm root -g` uses). Node's resolver looks for
151
- * `<parent>/node_modules/<pkg>` for each parent dir it walks up, so the
152
- * createRequire base must live in the root's PARENT directory — from
153
- * `<root>/../_resolver.js` it correctly finds `<root>/smoltalk-llama-cpp/...`.
154
- * Exported for unit-testing with a controllable list of roots. */
155
- export declare function resolveSmoltalkLlamaCppFromRoots(roots: string[]): string | null;
156
- /** Resolve `smoltalk-llama-cpp` to the absolute path of its main entry,
157
- * searching:
158
- * 1. The local `require` paths walking up from this file (covers in-workspace
159
- * `pnpm add` and a user-project install).
160
- * 2. Each global `node_modules` root reported by `npm root -g` / `pnpm root -g`
161
- * (covers `npm i -g` and `pnpm add -g` — the documented install methods).
162
- *
163
- * Returns `null` if the package isn't reachable from any of those. */
164
- export declare function resolveSmoltalkLlamaCppEntry(): string | null;
165
146
  /** True if smoltalk-llama-cpp is reachable from the local require paths OR
166
147
  * from a global node_modules root (npm or pnpm). */
167
148
  export declare function _localModelsSupported(): boolean;
@@ -172,7 +153,11 @@ export declare function _localModelsSupported(): boolean;
172
153
  * works in the override scenario (otherwise the CLI would exit 1 even though
173
154
  * the underlying TS functions would happily run). */
174
155
  export declare function hasLocalModelSupport(): boolean;
175
- /** Register the llama-cpp provider into agency's own smoltalk. */
156
+ /** Register the llama-cpp provider into agency's own smoltalk. When called
157
+ * from inside a run (the agent's --local path), emit the `localModelLoaded`
158
+ * statelog event saying where the provider package came from; the plain CLI
159
+ * has no runtime frame, so `__ctx()` is undefined there and nothing is
160
+ * emitted. */
176
161
  export declare function _registerLocalProvider(): Promise<void>;
177
162
  /** Stream-hash a file's SHA-256 (hex), never buffering the whole file. The
178
163
  * `update` is guarded so a synchronous throw in the data handler rejects the
@@ -203,6 +188,23 @@ export declare function _registerLocalModel(value: string, cacheDir?: string): P
203
188
  export declare function formatGB(bytes: number): string;
204
189
  /** Context window in compact units: 8192 → "8K", 131072 → "128K", 1e7 → "10M". */
205
190
  export declare function formatCtx(tokens: number): string;
191
+ /** The `agency local list` view: every usable model (curated + aliases) with
192
+ * a downloaded marker, then cache-dir files no catalog entry claims. A row
193
+ * is "downloaded" when the manifest maps its target URI to a file that still
194
+ * exists in the cache dir; its SIZE column then shows the on-disk size
195
+ * rather than the catalog estimate. Compact and operational — `alias list`
196
+ * keeps the verbose per-model catalog with descriptions. Returns the block
197
+ * with no trailing newline (the caller's `console.log` adds exactly one). */
198
+ export declare function formatLocalList(args: {
199
+ dir: string;
200
+ entries: ModelNameEntry[];
201
+ manifest: Record<string, string>;
202
+ files: {
203
+ name: string;
204
+ path: string;
205
+ sizeBytes: number;
206
+ }[];
207
+ }): string;
206
208
  /** Render the usable-model list as an aligned table: a header row plus one
207
209
  * fact row per curated model (params, category, size, context window,
208
210
  * license), the description on a dimmed line below, a blank line between