akm-cli 0.9.7 → 0.9.8-beta.2
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/CHANGELOG.md +310 -0
- package/dist/commands/env/env.js +2 -21
- package/dist/commands/health/checks.js +0 -25
- package/dist/commands/health/data-dir-usage.js +165 -0
- package/dist/commands/health/improve-metrics.js +8 -34
- package/dist/commands/health/windows.js +0 -4
- package/dist/commands/health.js +18 -37
- package/dist/commands/improve/consolidate/eligibility.js +11 -5
- package/dist/commands/improve/extract.js +36 -32
- package/dist/commands/improve/memory/memory-belief.js +15 -5
- package/dist/commands/improve/memory/memory-contradiction-detect.js +16 -25
- package/dist/commands/improve/memory/memory-improve.js +7 -19
- package/dist/commands/improve/preparation.js +52 -36
- package/dist/commands/improve/reflect.js +13 -17
- package/dist/commands/lint/base-linter.js +42 -2
- package/dist/commands/lint/index.js +4 -5
- package/dist/commands/migrate/config-extra-params.js +61 -0
- package/dist/commands/migrate/dead-residue.js +113 -0
- package/dist/commands/migrate/stale-txn.js +49 -0
- package/dist/commands/migrate-cli.js +42 -1
- package/dist/commands/proposal/proposal.js +1 -21
- package/dist/commands/proposal/repository.js +0 -4
- package/dist/commands/proposal/validators/proposal-quality-validators.js +0 -6
- package/dist/commands/read/curate.js +4 -18
- package/dist/commands/read/show.js +2 -1
- package/dist/commands/sources/installed-stashes.js +5 -1
- package/dist/commands/sources/self-update.js +38 -1
- package/dist/commands/sources/sources-cli.js +17 -1
- package/dist/commands/tasks/tasks.js +0 -8
- package/dist/commands/url-checker.js +20 -28
- package/dist/commands/workflow-cli.js +11 -61
- package/dist/core/adapter/adapters/agent-skills-adapter.js +2 -6
- package/dist/core/adapter/adapters/akm-adapter.js +2 -3
- package/dist/core/adapter/adapters/akm-lint.js +2 -20
- package/dist/core/adapter/adapters/akm-task-adapter.js +2 -6
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +2 -6
- package/dist/core/adapter/adapters/dotenv-adapter.js +2 -22
- package/dist/core/adapter/adapters/generic-files-adapter.js +3 -9
- package/dist/core/adapter/adapters/llm-wiki-adapter.js +2 -6
- package/dist/core/adapter/adapters/okf-adapter.js +7 -18
- package/dist/core/adapter/adapters/shared.js +2 -0
- package/dist/core/adapter/adapters/tool-dir-shared.js +3 -9
- package/dist/core/adapter/adapters/website-snapshot-adapter.js +2 -6
- package/dist/core/adapter/validate-context.js +1 -3
- package/dist/core/asset/asset-placement.js +1 -3
- package/dist/core/asset/frontmatter.js +96 -0
- package/dist/core/common.js +66 -2
- package/dist/core/config/config-io.js +21 -8
- package/dist/core/config/config-walker.js +6 -10
- package/dist/core/config/config.js +11 -8
- package/dist/core/extra-params.js +17 -13
- package/dist/core/improve-result.js +1 -3
- package/dist/core/json-schema.js +9 -11
- package/dist/core/state/migrations.js +74 -2
- package/dist/core/state-db.js +2 -1
- package/dist/core/warn.js +15 -0
- package/dist/execution/executable-identity.js +1 -3
- package/dist/execution/guarded-source.js +1 -6
- package/dist/indexer/bundle-identity-guard.js +6 -1
- package/dist/indexer/db/graph-db.js +139 -154
- package/dist/indexer/ensure-index.js +11 -19
- package/dist/indexer/graph/graph-boost.js +23 -34
- package/dist/indexer/graph/graph-extraction.js +12 -2
- package/dist/indexer/indexer.js +43 -22
- package/dist/indexer/lookup/adapter-concept-owner.js +12 -9
- package/dist/indexer/passes/dir-staleness.js +30 -9
- package/dist/indexer/passes/memory-inference.js +7 -2
- package/dist/indexer/scan/drain-dir.js +2 -4
- package/dist/indexer/search/db-search.js +3 -3
- package/dist/indexer/search/fts-query.js +10 -15
- package/dist/indexer/search/search-source.js +0 -13
- package/dist/indexer/usage/usage-events.js +9 -1
- package/dist/indexer/walk/walker.js +11 -6
- package/dist/integrations/harnesses/claude/result-extractor.js +30 -15
- package/dist/integrations/harnesses/codex/result-extractor.js +43 -6
- package/dist/integrations/harnesses/copilot/result-extractor.js +39 -12
- package/dist/integrations/harnesses/gemini/result-extractor.js +40 -12
- package/dist/integrations/harnesses/index.js +0 -4
- package/dist/integrations/harnesses/openhands/result-extractor.js +51 -19
- package/dist/integrations/harnesses/pi/result-extractor.js +45 -12
- package/dist/integrations/lockfile.js +0 -14
- package/dist/integrations/session-logs/index.js +0 -81
- package/dist/llm/client.js +0 -12
- package/dist/llm/memory-infer.js +1 -3
- package/dist/llm/usage-telemetry.js +1 -3
- package/dist/registry/create-provider-registry.js +4 -0
- package/dist/registry/factory.js +4 -0
- package/dist/registry/resolve.js +23 -8
- package/dist/runtime.js +0 -12
- package/dist/scripts/akm-migrate-node.js +179 -248
- package/dist/scripts/akm-migrate.js +179 -248
- package/dist/setup/setup.js +6 -12
- package/dist/sources/providers/git-install.js +7 -2
- package/dist/sources/providers/tar-utils.js +1 -7
- package/dist/sources/snapshot-fetchers/content-extract.js +0 -17
- package/dist/storage/managed-db.js +20 -7
- package/dist/storage/repositories/events-repository.js +0 -81
- package/dist/storage/repositories/index-entries-repository.js +24 -28
- package/dist/storage/repositories/index-fts-repository.js +24 -30
- package/dist/storage/repositories/index-meta-repository.js +6 -4
- package/dist/storage/repositories/index-schema.js +16 -1
- package/dist/storage/repositories/index-utility-repository.js +0 -57
- package/dist/storage/repositories/index-vec-repository.js +25 -27
- package/dist/storage/repositories/proposals-repository.js +4 -1
- package/dist/storage/repositories/task-history-repository.js +9 -3
- package/dist/tasks/backends/launchd.js +8 -18
- package/dist/tasks/resolve-akm-bin.js +15 -0
- package/dist/tasks/run/task-history.js +21 -31
- package/dist/tasks/scheduler-sync.js +1 -6
- package/dist/tasks/source/bounded-document.js +1 -14
- package/dist/tasks/source/task-to-v3.js +10 -2
- package/dist/workflows/exec/child-workflow.js +1 -1
- package/dist/workflows/exec/native-executor.js +2 -2
- package/dist/workflows/exec/step-work.js +5 -17
- package/dist/workflows/exec/worktree.js +40 -6
- package/dist/workflows/freeze/task-bindings.js +2 -4
- package/dist/workflows/ir/compile.js +3 -14
- package/dist/workflows/ir/schema-v4.js +4 -6
- package/dist/workflows/ir/schema.js +2 -5
- package/dist/workflows/parser.js +23 -26
- package/dist/workflows/source-files.js +8 -13
- package/dist/workflows/source-ir/schema.js +1 -19
- package/docs/migration/v0.9.0-troubleshooting.md +12 -2
- package/docs/migration/v0.9.1-to-v0.9.2.md +18 -4
- package/docs/reference/cli.md +3 -1
- package/docs/reference/data-and-telemetry.md +1 -0
- package/docs/reference/tasks.md +11 -0
- package/docs/reference/workflow-schema.md +24 -0
- package/package.json +2 -9
- package/dist/workflows/source-ir/compare.js +0 -17
|
@@ -1,15 +1,56 @@
|
|
|
1
1
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
/**
|
|
5
|
+
* OpenHands CLI result extractor (P2, plan §"The adapter contract" step 3 /
|
|
6
|
+
* §"Structured-output normalization", tier "native-json").
|
|
7
|
+
*
|
|
8
|
+
* Normalizes one raw {@link AgentRunResult} from a headless
|
|
9
|
+
* `openhands --headless --json` run into `{ text, sessionId? }` — the
|
|
10
|
+
* {@link AgentResultExtraction} seam. The engine's shared schema-validation /
|
|
11
|
+
* retry-until-valid loop runs *after* this; the extractor only strips
|
|
12
|
+
* transport framing.
|
|
13
|
+
*
|
|
14
|
+
* With `--json` OpenHands emits ONE JSON event per stdout line — its
|
|
15
|
+
* action/observation event stream, e.g.:
|
|
16
|
+
*
|
|
17
|
+
* {"id":0,"source":"user","action":"message","args":{"content":"<task>"},"message":"<task>"}
|
|
18
|
+
* {"id":1,"source":"agent","action":"run","args":{"command":"ls"},"message":"Running command: ls"}
|
|
19
|
+
* {"id":2,"source":"agent","observation":"run","content":"README.md","message":"Command `ls` executed."}
|
|
20
|
+
* {"id":3,"source":"agent","action":"message","args":{"content":"<answer>"},"message":"<answer>"}
|
|
21
|
+
* {"id":4,"source":"agent","action":"finish","args":{"final_thought":"<answer>","outputs":{}},"message":"..."}
|
|
22
|
+
*
|
|
23
|
+
* Extraction rules ("parse JSONL final message" per the capability matrix):
|
|
24
|
+
* - text: the LAST agent-sourced *message-bearing* event wins. Only two
|
|
25
|
+
* event kinds carry the agent's answer: `action:"message"`
|
|
26
|
+
* (`args.content`, falling back to the top-level `message` mirror) and
|
|
27
|
+
* `action:"finish"` (`args.final_thought`/`args.thought`, falling back to
|
|
28
|
+
* `message`). User echoes, observations, and tool actions (`run`, `edit`,
|
|
29
|
+
* …, whose `message` is progress noise like "Running command: ls") never
|
|
30
|
+
* contribute.
|
|
31
|
+
* - sessionId: the FIRST id-bearing event supplies it (`session_id`,
|
|
32
|
+
* `sessionId`, `sid`, `conversation_id`), falling back to any sessionId
|
|
33
|
+
* the spawn layer already attached. Stored opportunistically on the unit
|
|
34
|
+
* row; per the matrix OpenHands resume is workspace-state, so akm never
|
|
35
|
+
* depends on it (plan §"Session, MCP, and identity across harnesses").
|
|
36
|
+
*
|
|
37
|
+
* Also handled, so version churn stays contained in this one file (per the
|
|
38
|
+
* adapter contract):
|
|
39
|
+
* - a single whole-stdout JSON document (or a spawn-layer `result.parsed`
|
|
40
|
+
* value) is interpreted with the same rules;
|
|
41
|
+
* - plain text (no `--json`, or an unrecognized envelope) passes through
|
|
42
|
+
* trimmed — the engine's embedded-JSON parsing still runs downstream for
|
|
43
|
+
* schema units;
|
|
44
|
+
* - non-JSON banner lines interleaved in the stream are skipped.
|
|
45
|
+
*
|
|
46
|
+
* Registered: `openhandsResultExtractor` is `OpenhandsHarness.resultExtractor`
|
|
47
|
+
* (`./index.ts`); the engine resolves it via `getHarness()` in
|
|
48
|
+
* `workflows/exec/native-executor.ts` to normalize agent-unit stdout before
|
|
49
|
+
* schema validation.
|
|
50
|
+
*/
|
|
51
|
+
import { asNonEmptyString, isRecord, tryParseJson } from "../../../core/common.js";
|
|
7
52
|
/** Keys that may carry the harness-native session id, in precedence order. */
|
|
8
53
|
const SESSION_KEYS = ["session_id", "sessionId", "sid", "conversation_id"];
|
|
9
|
-
/** Return a non-empty string, else undefined. */
|
|
10
|
-
function nonEmptyString(value) {
|
|
11
|
-
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
12
|
-
}
|
|
13
54
|
/**
|
|
14
55
|
* Extract the agent's answer text from one parsed OpenHands event, if the
|
|
15
56
|
* event is one of the two message-bearing kinds (see module doc). Everything
|
|
@@ -25,31 +66,22 @@ function extractAgentText(event) {
|
|
|
25
66
|
return undefined;
|
|
26
67
|
const args = isRecord(event.args) ? event.args : undefined;
|
|
27
68
|
if (event.action === "message") {
|
|
28
|
-
return
|
|
69
|
+
return asNonEmptyString(args?.content) ?? asNonEmptyString(event.message);
|
|
29
70
|
}
|
|
30
71
|
if (event.action === "finish") {
|
|
31
|
-
return
|
|
72
|
+
return asNonEmptyString(args?.final_thought) ?? asNonEmptyString(args?.thought) ?? asNonEmptyString(event.message);
|
|
32
73
|
}
|
|
33
74
|
return undefined;
|
|
34
75
|
}
|
|
35
76
|
/** Extract a harness-native session id from one parsed JSON value, if any. */
|
|
36
77
|
function extractSessionId(event) {
|
|
37
78
|
for (const key of SESSION_KEYS) {
|
|
38
|
-
const candidate =
|
|
79
|
+
const candidate = asNonEmptyString(event[key]);
|
|
39
80
|
if (candidate)
|
|
40
81
|
return candidate;
|
|
41
82
|
}
|
|
42
83
|
return undefined;
|
|
43
84
|
}
|
|
44
|
-
/** JSON.parse that returns undefined instead of throwing. */
|
|
45
|
-
function tryParseJson(raw) {
|
|
46
|
-
try {
|
|
47
|
-
return JSON.parse(raw);
|
|
48
|
-
}
|
|
49
|
-
catch {
|
|
50
|
-
return undefined;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
85
|
/**
|
|
54
86
|
* Normalize a raw openhands run result into `{ text, sessionId? }`.
|
|
55
87
|
* See the module doc for the stdout shapes handled.
|
|
@@ -1,9 +1,51 @@
|
|
|
1
1
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Pi coding-agent CLI result extractor (P2, plan §"The adapter contract"
|
|
6
|
+
* step 3 / §"Structured-output normalization", tier "native-json").
|
|
7
|
+
*
|
|
8
|
+
* Normalizes one raw {@link AgentRunResult} from a headless `pi` run into
|
|
9
|
+
* `{ text, sessionId? }` — the {@link AgentResultExtraction} seam. The
|
|
10
|
+
* engine's shared schema-validation / retry-until-valid loop runs *after*
|
|
11
|
+
* this; the extractor only strips transport framing.
|
|
12
|
+
*
|
|
13
|
+
* With `--mode json` (the capability matrix's structured mode) Pi emits ONE
|
|
14
|
+
* JSON event per stdout line — the agent-session event stream, e.g.:
|
|
15
|
+
*
|
|
16
|
+
* {"type":"session_start","session_id":"<id>"}
|
|
17
|
+
* {"type":"agent_start"}
|
|
18
|
+
* {"type":"message_start","message":{"role":"assistant","content":[]}}
|
|
19
|
+
* {"type":"message_end","message":{"role":"assistant","content":[{"type":"text","text":"<text>"}]}}
|
|
20
|
+
* {"type":"agent_end","messages":[{"role":"assistant","content":[...]}]}
|
|
21
|
+
*
|
|
22
|
+
* Extraction rules:
|
|
23
|
+
* - text: the LAST assistant text-bearing event wins (`message_end` bodies;
|
|
24
|
+
* an `agent_end` transcript's final assistant message; tolerant flat
|
|
25
|
+
* `role:"assistant"` events). User-role echoes / tool events never
|
|
26
|
+
* contribute. Assistant content blocks are flattened; non-text blocks
|
|
27
|
+
* (thinking, tool use) are skipped.
|
|
28
|
+
* - sessionId: the FIRST session-id-bearing event supplies it (snake_case /
|
|
29
|
+
* camelCase variants, plus `id` on `session*`-typed events), falling back
|
|
30
|
+
* to any sessionId the spawn layer already attached. Stored
|
|
31
|
+
* opportunistically on the unit row for `--session <id>` resume; akm
|
|
32
|
+
* never depends on it (plan §"Session, MCP, and identity").
|
|
33
|
+
*
|
|
34
|
+
* Also handled, so version churn stays contained in this one file (per the
|
|
35
|
+
* adapter contract):
|
|
36
|
+
* - a single whole-stdout JSON document (or a spawn-layer `result.parsed`
|
|
37
|
+
* value) is interpreted with the same rules;
|
|
38
|
+
* - plain text (no `--mode json`, or an unrecognized envelope) passes
|
|
39
|
+
* through trimmed — the engine's embedded-JSON parsing still runs
|
|
40
|
+
* downstream for schema units;
|
|
41
|
+
* - non-JSON banner lines interleaved in the stream are skipped.
|
|
42
|
+
*
|
|
43
|
+
* Registered: `piResultExtractor` is `PiHarness.resultExtractor`
|
|
44
|
+
* (`./index.ts`); the engine resolves it via `getHarness()` in
|
|
45
|
+
* `workflows/exec/native-executor.ts` to normalize agent-unit stdout before
|
|
46
|
+
* schema validation.
|
|
47
|
+
*/
|
|
48
|
+
import { isRecord, tryParseJson } from "../../../core/common.js";
|
|
7
49
|
/** Keys that may carry the harness-native session id, in precedence order. */
|
|
8
50
|
const SESSION_KEYS = ["session_id", "sessionId", "session"];
|
|
9
51
|
/**
|
|
@@ -73,15 +115,6 @@ function extractSessionId(event) {
|
|
|
73
115
|
}
|
|
74
116
|
return undefined;
|
|
75
117
|
}
|
|
76
|
-
/** JSON.parse that returns undefined instead of throwing. */
|
|
77
|
-
function tryParseJson(raw) {
|
|
78
|
-
try {
|
|
79
|
-
return JSON.parse(raw);
|
|
80
|
-
}
|
|
81
|
-
catch {
|
|
82
|
-
return undefined;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
118
|
/**
|
|
86
119
|
* Normalize a raw pi run result into `{ text, sessionId? }`.
|
|
87
120
|
* See the module doc for the stdout shapes handled.
|
|
@@ -186,20 +186,6 @@ export async function writeLockfile(entries) {
|
|
|
186
186
|
release();
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
|
-
/** Replace one exact parsed lock generation while the lockfile sentinel is held. */
|
|
190
|
-
export async function compareAndSwapLockfile(expected, desired) {
|
|
191
|
-
const release = await acquireLockSentinel();
|
|
192
|
-
try {
|
|
193
|
-
const current = readLockfileOrThrow();
|
|
194
|
-
if (JSON.stringify(current) !== JSON.stringify(expected))
|
|
195
|
-
return false;
|
|
196
|
-
writeLockfileUnlocked(desired);
|
|
197
|
-
return true;
|
|
198
|
-
}
|
|
199
|
-
finally {
|
|
200
|
-
release();
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
189
|
/**
|
|
204
190
|
* Publish an update from one exact raw + parsed lockfile generation and return
|
|
205
191
|
* the exact bytes that were written. Formatting-only concurrent edits are a
|
|
@@ -39,7 +39,6 @@ for (const provider of HARNESSES) {
|
|
|
39
39
|
throw new Error(`[akm] session-log provider "${provider.name}" is not registered as a sessionLogs harness in HARNESS_REGISTRY (src/integrations/harnesses). Add it there.`);
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
-
const ERROR_PATTERNS = /error|failed|exception|cannot|undefined|null pointer|ENOENT|timeout/i;
|
|
43
42
|
/**
|
|
44
43
|
* Returns all available session log harnesses for the current machine.
|
|
45
44
|
* Add new harnesses to HARNESSES to support additional agent runtimes.
|
|
@@ -53,83 +52,3 @@ export function normalizeSessionTopic(text) {
|
|
|
53
52
|
return undefined;
|
|
54
53
|
return normalized.slice(0, 60);
|
|
55
54
|
}
|
|
56
|
-
export function aggregateSessionEvents(events) {
|
|
57
|
-
const counts = new Map();
|
|
58
|
-
for (const event of events) {
|
|
59
|
-
const topic = normalizeSessionTopic(event.text);
|
|
60
|
-
if (!topic)
|
|
61
|
-
continue;
|
|
62
|
-
const isFailurePattern = ERROR_PATTERNS.test(topic);
|
|
63
|
-
if (!isFailurePattern)
|
|
64
|
-
continue;
|
|
65
|
-
const existing = counts.get(topic) ?? {
|
|
66
|
-
count: 0,
|
|
67
|
-
isFailurePattern,
|
|
68
|
-
sources: new Set(),
|
|
69
|
-
topic,
|
|
70
|
-
};
|
|
71
|
-
existing.count += 1;
|
|
72
|
-
existing.isFailurePattern = existing.isFailurePattern || isFailurePattern;
|
|
73
|
-
existing.sources.add(event.harness);
|
|
74
|
-
counts.set(topic, existing);
|
|
75
|
-
}
|
|
76
|
-
return [...counts.values()]
|
|
77
|
-
.filter((entry) => entry.count >= 2)
|
|
78
|
-
.sort((a, b) => b.count - a.count || a.topic.localeCompare(b.topic))
|
|
79
|
-
.slice(0, 15)
|
|
80
|
-
.map((entry) => ({
|
|
81
|
-
topic: entry.topic,
|
|
82
|
-
frequency: entry.count,
|
|
83
|
-
source: [...entry.sources].sort().join(","),
|
|
84
|
-
isFailurePattern: entry.isFailurePattern,
|
|
85
|
-
}));
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Collect normalized session events from a set of harnesses for the health
|
|
89
|
-
* candidate scan (#568).
|
|
90
|
-
*
|
|
91
|
-
* Pipeline selection per harness:
|
|
92
|
-
* - Drive the richer `listSessions()` + `readSession()` pipeline. `readSession` flattens
|
|
93
|
-
* structured content — tool calls, assistant content blocks, thinking,
|
|
94
|
-
* tool_result — into event text (e.g. ClaudeCodeProvider's `parseClaudeEvent`
|
|
95
|
-
* surfaces `[tool:*]` / `[tool_result]` blocks that the legacy flat
|
|
96
|
-
* `readEvents` scan drops entirely). This is what lets health advisories see
|
|
97
|
-
* repeated tool failures / long runs that the flat scan hid.
|
|
98
|
-
*
|
|
99
|
-
* Extracted as a pure function (harnesses injected) so it is unit-testable
|
|
100
|
-
* without touching the real on-disk session-log locations.
|
|
101
|
-
*
|
|
102
|
-
* `maxSessionsPerHarness` bounds the path: `readSession()` reads each
|
|
103
|
-
* session file in full. On a machine with a
|
|
104
|
-
* deep `~/.claude/projects` history a 30-day window can hold hundreds of
|
|
105
|
-
* multi-MB session files, and reading+parsing every one in full made the
|
|
106
|
-
* health command (`akm health`, which calls this synchronously) blow past its
|
|
107
|
-
* latency budget. `listSessions()` returns summaries sorted newest-first, so
|
|
108
|
-
* capping to the most-recent N sessions per harness keeps the richer signal
|
|
109
|
-
* for what actually matters (recent activity) while bounding cost.
|
|
110
|
-
*/
|
|
111
|
-
const DEFAULT_MAX_SESSIONS_PER_HARNESS = 50;
|
|
112
|
-
export function collectSessionEvents(harnesses, sinceMs, maxSessionsPerHarness = DEFAULT_MAX_SESSIONS_PER_HARNESS) {
|
|
113
|
-
const events = [];
|
|
114
|
-
for (const harness of harnesses) {
|
|
115
|
-
try {
|
|
116
|
-
// Enumerate sessions cheaply, then read each one's full structured event
|
|
117
|
-
// stream. There is no parallel flat-log parser.
|
|
118
|
-
const summaries = harness.listSessions({ sinceMs });
|
|
119
|
-
// summaries are newest-first; bound the full-file reads (see doc above).
|
|
120
|
-
for (const summary of summaries.slice(0, maxSessionsPerHarness)) {
|
|
121
|
-
try {
|
|
122
|
-
const session = harness.readSession(summary);
|
|
123
|
-
events.push(...session.events);
|
|
124
|
-
}
|
|
125
|
-
catch {
|
|
126
|
-
// a single unreadable session is non-fatal
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
catch {
|
|
131
|
-
// individual harness failures are non-fatal
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
return events;
|
|
135
|
-
}
|
package/dist/llm/client.js
CHANGED
|
@@ -423,18 +423,6 @@ async function chatCompletionAttemptOnce(config, messages, options, timeoutMs, i
|
|
|
423
423
|
}
|
|
424
424
|
}
|
|
425
425
|
// ── Availability check ──────────────────────────────────────────────────────
|
|
426
|
-
/**
|
|
427
|
-
* Check if the LLM endpoint is reachable.
|
|
428
|
-
*/
|
|
429
|
-
export async function isLlmAvailable(config) {
|
|
430
|
-
try {
|
|
431
|
-
const result = await chatCompletion(config, [{ role: "user", content: "Respond with just the word: ok" }]);
|
|
432
|
-
return result.length > 0;
|
|
433
|
-
}
|
|
434
|
-
catch {
|
|
435
|
-
return false;
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
426
|
// ── Reachability probe ──────────────────────────────────────────────────────
|
|
439
427
|
/**
|
|
440
428
|
* Best-effort reachability check with an error message, for setup's optional
|
package/dist/llm/memory-infer.js
CHANGED
|
@@ -22,8 +22,6 @@ import { toErrorMessage } from "../core/common.js";
|
|
|
22
22
|
import { parseEmbeddedJsonResponse } from "../core/parse.js";
|
|
23
23
|
import { warn } from "../core/warn.js";
|
|
24
24
|
import { callStructured } from "./structured-call.js";
|
|
25
|
-
/** Hard cap on body chars sent to the model — pragmatic and matches `runLlmEnrich`. */
|
|
26
|
-
const MAX_BODY_CHARS = 4000;
|
|
27
25
|
const SYSTEM_PROMPT = memoryInferSystemPrompt;
|
|
28
26
|
const USER_PROMPT_PREFIX = memoryInferUserPrompt;
|
|
29
27
|
const PROMPT_PLACEHOLDERS = new Set([
|
|
@@ -74,7 +72,7 @@ export async function compressMemoryToDerivedMemory(llmRunner, body, signal, akm
|
|
|
74
72
|
const trimmedBody = body.trim();
|
|
75
73
|
if (!trimmedBody)
|
|
76
74
|
return undefined;
|
|
77
|
-
const userPrompt = `${USER_PROMPT_PREFIX}${trimmedBody
|
|
75
|
+
const userPrompt = `${USER_PROMPT_PREFIX}${trimmedBody}`;
|
|
78
76
|
// Memory inference is always gated: no config closes the gate instead of
|
|
79
77
|
// taking callStructured's ungated path for direct callers.
|
|
80
78
|
if (!akmConfig) {
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
* `undefined` outside any `withLlmStage` scope.
|
|
26
26
|
*/
|
|
27
27
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
28
|
+
import { asNonEmptyString } from "../core/common.js";
|
|
28
29
|
const attributionStorage = new AsyncLocalStorage();
|
|
29
30
|
let usageSink;
|
|
30
31
|
/**
|
|
@@ -85,9 +86,6 @@ export function emitLlmUsage(record) {
|
|
|
85
86
|
function asFiniteNonNegative(value) {
|
|
86
87
|
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
87
88
|
}
|
|
88
|
-
function asNonEmptyString(value) {
|
|
89
|
-
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
90
|
-
}
|
|
91
89
|
function asLlmUsageErrorCode(value) {
|
|
92
90
|
switch (value) {
|
|
93
91
|
case "rate_limited":
|
|
@@ -17,6 +17,10 @@ export function createProviderRegistry() {
|
|
|
17
17
|
resolve(type) {
|
|
18
18
|
return map.get(type) ?? null;
|
|
19
19
|
},
|
|
20
|
+
/** Removes a registration. No-op if `type` was never registered. */
|
|
21
|
+
unregister(type) {
|
|
22
|
+
map.delete(type);
|
|
23
|
+
},
|
|
20
24
|
/** Snapshot of all registered keys. Iteration order matches insertion order. */
|
|
21
25
|
list() {
|
|
22
26
|
return [...map.keys()];
|
package/dist/registry/factory.js
CHANGED
|
@@ -24,3 +24,7 @@ export function registerRegistryProvider(type, factory) {
|
|
|
24
24
|
export function resolveRegistryProviderFactory(type) {
|
|
25
25
|
return registry.resolve(type);
|
|
26
26
|
}
|
|
27
|
+
/** Test-only seam: removes a registration made with {@link registerRegistryProvider}. */
|
|
28
|
+
export function unregisterRegistryProvider(type) {
|
|
29
|
+
registry.unregister(type);
|
|
30
|
+
}
|
package/dist/registry/resolve.js
CHANGED
|
@@ -298,14 +298,18 @@ export function trustedNpmTarballHosts() {
|
|
|
298
298
|
const hosts = new Set([DEFAULT_NPM_REGISTRY_HOST]);
|
|
299
299
|
const override = process.env.AKM_NPM_REGISTRY?.trim();
|
|
300
300
|
if (override) {
|
|
301
|
+
// A malformed override must not be silently ignored (falling back to the
|
|
302
|
+
// public registry as though nothing was configured) — that would install
|
|
303
|
+
// from the wrong registry without telling the operator (R-035).
|
|
304
|
+
let overrideHost;
|
|
301
305
|
try {
|
|
302
|
-
|
|
303
|
-
if (overrideHost)
|
|
304
|
-
hosts.add(overrideHost);
|
|
306
|
+
overrideHost = new URL(override).hostname.toLowerCase();
|
|
305
307
|
}
|
|
306
308
|
catch {
|
|
307
|
-
|
|
309
|
+
throw new UsageError(`AKM_NPM_REGISTRY is set to an invalid URL: ${override}`);
|
|
308
310
|
}
|
|
311
|
+
if (overrideHost)
|
|
312
|
+
hosts.add(overrideHost);
|
|
309
313
|
}
|
|
310
314
|
return hosts;
|
|
311
315
|
}
|
|
@@ -358,14 +362,18 @@ export function npmArtifactNetworkPolicy(artifact) {
|
|
|
358
362
|
function npmMetadataRegistry() {
|
|
359
363
|
const override = process.env.AKM_NPM_REGISTRY?.trim();
|
|
360
364
|
if (override) {
|
|
365
|
+
// A malformed override must not be silently ignored (falling back to the
|
|
366
|
+
// public registry as though nothing was configured) — that would install
|
|
367
|
+
// from the wrong registry without telling the operator (R-035).
|
|
368
|
+
let url;
|
|
361
369
|
try {
|
|
362
|
-
|
|
363
|
-
const base = `${url.origin}${url.pathname === "/" ? "" : url.pathname}`;
|
|
364
|
-
return { baseUrl: base.replace(/\/+$/, ""), allowPrivateRegistryOrigin: true };
|
|
370
|
+
url = new URL(override);
|
|
365
371
|
}
|
|
366
372
|
catch {
|
|
367
|
-
|
|
373
|
+
throw new UsageError(`AKM_NPM_REGISTRY is set to an invalid URL: ${override}`);
|
|
368
374
|
}
|
|
375
|
+
const base = `${url.origin}${url.pathname === "/" ? "" : url.pathname}`;
|
|
376
|
+
return { baseUrl: base.replace(/\/+$/, ""), allowPrivateRegistryOrigin: true };
|
|
369
377
|
}
|
|
370
378
|
return { baseUrl: `https://${DEFAULT_NPM_REGISTRY_HOST}`, allowPrivateRegistryOrigin: false };
|
|
371
379
|
}
|
|
@@ -490,6 +498,13 @@ function resolveGitRevisionFromRemote(url, requestedRef) {
|
|
|
490
498
|
}
|
|
491
499
|
async function resolveGitArtifact(parsed) {
|
|
492
500
|
const resolvedRevision = resolveGitRevisionFromRemote(parsed.url, parsed.requestedRef);
|
|
501
|
+
if (!resolvedRevision) {
|
|
502
|
+
// Unlike the GitHub path (which falls back to the REST API on a failed
|
|
503
|
+
// `git ls-remote`), a plain git source has no fallback resolver — an
|
|
504
|
+
// unresolved revision here would silently disable the post-clone
|
|
505
|
+
// revision-integrity check in `verifyClonedRevision` (R-011).
|
|
506
|
+
throw new Error(`Unable to resolve ${parsed.requestedRef ?? "HEAD"} for ${parsed.url} via 'git ls-remote'; refusing to install without a verifiable revision.`);
|
|
507
|
+
}
|
|
493
508
|
return {
|
|
494
509
|
id: parsed.id,
|
|
495
510
|
source: parsed.source,
|
package/dist/runtime.js
CHANGED
|
@@ -250,18 +250,6 @@ export function semverOrder(a, b) {
|
|
|
250
250
|
const semver = nodeRequire("semver");
|
|
251
251
|
return semver.compare(a, b);
|
|
252
252
|
}
|
|
253
|
-
// ── Module resolution ───────────────────────────────────────────────────────
|
|
254
|
-
/**
|
|
255
|
-
* Resolve a module specifier without loading it, relative to `from`. On Bun
|
|
256
|
-
* uses `Bun.resolveSync`; on Node uses `require.resolve`. Throws when the
|
|
257
|
-
* specifier cannot be resolved (mirroring both underlying APIs).
|
|
258
|
-
*/
|
|
259
|
-
export function resolveModule(spec, from) {
|
|
260
|
-
if (isBun) {
|
|
261
|
-
return bunGlobal().resolveSync(spec, from);
|
|
262
|
-
}
|
|
263
|
-
return nodeRequire.resolve(spec, { paths: [from] });
|
|
264
|
-
}
|
|
265
253
|
// ── Filesystem paths ────────────────────────────────────────────────────────
|
|
266
254
|
/**
|
|
267
255
|
* The directory of the module identified by `importMetaUrl`. Replaces
|