@namzu/sdk 62.6.1 → 62.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/dist/agents/QueryAgent.d.ts.map +1 -1
- package/dist/agents/QueryAgent.js +5 -0
- package/dist/agents/QueryAgent.js.map +1 -1
- package/dist/agents/examples/SupervisorAgent.d.ts.map +1 -1
- package/dist/agents/examples/SupervisorAgent.js +1 -0
- package/dist/agents/examples/SupervisorAgent.js.map +1 -1
- package/dist/agents/explore.d.ts.map +1 -1
- package/dist/agents/explore.js +2 -1
- package/dist/agents/explore.js.map +1 -1
- package/dist/compaction/index.d.ts +1 -1
- package/dist/compaction/index.d.ts.map +1 -1
- package/dist/compaction/index.js +1 -1
- package/dist/compaction/index.js.map +1 -1
- package/dist/compaction/model-summary.js +2 -2
- package/dist/compaction/model-summary.js.map +1 -1
- package/dist/compaction/summary.d.ts +16 -1
- package/dist/compaction/summary.d.ts.map +1 -1
- package/dist/compaction/summary.js +25 -2
- package/dist/compaction/summary.js.map +1 -1
- package/dist/config/runtime.d.ts +8 -0
- package/dist/config/runtime.d.ts.map +1 -1
- package/dist/config/runtime.js +15 -0
- package/dist/config/runtime.js.map +1 -1
- package/dist/execution/workflow/runtime.d.ts.map +1 -1
- package/dist/execution/workflow/runtime.js +3 -0
- package/dist/execution/workflow/runtime.js.map +1 -1
- package/dist/execution/workflow/types.d.ts +8 -0
- package/dist/execution/workflow/types.d.ts.map +1 -1
- package/dist/manager/agent/child-session.d.ts +12 -0
- package/dist/manager/agent/child-session.d.ts.map +1 -1
- package/dist/manager/agent/child-session.js +29 -0
- package/dist/manager/agent/child-session.js.map +1 -1
- package/dist/manager/agent/lifecycle.d.ts.map +1 -1
- package/dist/manager/agent/lifecycle.js +8 -0
- package/dist/manager/agent/lifecycle.js.map +1 -1
- package/dist/manager/session/turn-recorder.d.ts +13 -2
- package/dist/manager/session/turn-recorder.d.ts.map +1 -1
- package/dist/manager/session/turn-recorder.js +57 -3
- package/dist/manager/session/turn-recorder.js.map +1 -1
- package/dist/prompt/coding-agent-doctrine.d.ts +3 -3
- package/dist/prompt/coding-agent-doctrine.d.ts.map +1 -1
- package/dist/prompt/coding-agent-doctrine.js +5 -3
- package/dist/prompt/coding-agent-doctrine.js.map +1 -1
- package/dist/provider/fallback.d.ts.map +1 -1
- package/dist/provider/fallback.js +5 -1
- package/dist/provider/fallback.js.map +1 -1
- package/dist/provider/retry.d.ts +25 -0
- package/dist/provider/retry.d.ts.map +1 -1
- package/dist/provider/retry.js +28 -0
- package/dist/provider/retry.js.map +1 -1
- package/dist/public-runtime.d.ts +3 -3
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +3 -3
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-tools.d.ts +4 -2
- package/dist/public-tools.d.ts.map +1 -1
- package/dist/public-tools.js +5 -2
- package/dist/public-tools.js.map +1 -1
- package/dist/runtime/query/auto-review/classifier.d.ts +7 -1
- package/dist/runtime/query/auto-review/classifier.d.ts.map +1 -1
- package/dist/runtime/query/auto-review/classifier.js +8 -4
- package/dist/runtime/query/auto-review/classifier.js.map +1 -1
- package/dist/runtime/query/auto-review/prompt.d.ts.map +1 -1
- package/dist/runtime/query/auto-review/prompt.js +2 -0
- package/dist/runtime/query/auto-review/prompt.js.map +1 -1
- package/dist/runtime/query/auto-review/script-evidence.d.ts +93 -0
- package/dist/runtime/query/auto-review/script-evidence.d.ts.map +1 -0
- package/dist/runtime/query/auto-review/script-evidence.js +566 -0
- package/dist/runtime/query/auto-review/script-evidence.js.map +1 -0
- package/dist/runtime/query/checkpoint.d.ts +13 -0
- package/dist/runtime/query/checkpoint.d.ts.map +1 -1
- package/dist/runtime/query/checkpoint.js +37 -0
- package/dist/runtime/query/checkpoint.js.map +1 -1
- package/dist/runtime/query/executor.d.ts +38 -1
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +130 -11
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/finalize-turn.d.ts +7 -1
- package/dist/runtime/query/finalize-turn.d.ts.map +1 -1
- package/dist/runtime/query/finalize-turn.js +2 -0
- package/dist/runtime/query/finalize-turn.js.map +1 -1
- package/dist/runtime/query/index.d.ts +8 -0
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +7 -0
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts +32 -1
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +158 -10
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.d.ts +12 -0
- package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.js +48 -1
- package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
- package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/context.js +5 -0
- package/dist/runtime/query/iteration/phases/context.js.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.js +3 -3
- package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
- package/dist/runtime/query/prelude-lease.d.ts.map +1 -1
- package/dist/runtime/query/prelude-lease.js +22 -0
- package/dist/runtime/query/prelude-lease.js.map +1 -1
- package/dist/runtime/query/prepare-turn.d.ts.map +1 -1
- package/dist/runtime/query/prepare-turn.js +13 -4
- package/dist/runtime/query/prepare-turn.js.map +1 -1
- package/dist/runtime/query/review-policy.d.ts.map +1 -1
- package/dist/runtime/query/review-policy.js +29 -0
- package/dist/runtime/query/review-policy.js.map +1 -1
- package/dist/runtime/query/tooling.d.ts +2 -0
- package/dist/runtime/query/tooling.d.ts.map +1 -1
- package/dist/runtime/query/tooling.js +1 -0
- package/dist/runtime/query/tooling.js.map +1 -1
- package/dist/runtime/query/turn/completion-audit.d.ts +27 -1
- package/dist/runtime/query/turn/completion-audit.d.ts.map +1 -1
- package/dist/runtime/query/turn/completion-audit.js +71 -6
- package/dist/runtime/query/turn/completion-audit.js.map +1 -1
- package/dist/runtime/query/turn/outcome-ledger.d.ts +113 -0
- package/dist/runtime/query/turn/outcome-ledger.d.ts.map +1 -0
- package/dist/runtime/query/turn/outcome-ledger.js +578 -0
- package/dist/runtime/query/turn/outcome-ledger.js.map +1 -0
- package/dist/sandbox/glob-segment.d.ts +8 -1
- package/dist/sandbox/glob-segment.d.ts.map +1 -1
- package/dist/sandbox/glob-segment.js +28 -9
- package/dist/sandbox/glob-segment.js.map +1 -1
- package/dist/store/session-log/index.d.ts +1 -0
- package/dist/store/session-log/index.d.ts.map +1 -1
- package/dist/store/session-log/index.js +1 -0
- package/dist/store/session-log/index.js.map +1 -1
- package/dist/store/session-log/turn-outcome.d.ts +37 -0
- package/dist/store/session-log/turn-outcome.d.ts.map +1 -0
- package/dist/store/session-log/turn-outcome.js +77 -0
- package/dist/store/session-log/turn-outcome.js.map +1 -0
- package/dist/tools/builtins/bash.js +1 -1
- package/dist/tools/builtins/bash.js.map +1 -1
- package/dist/tools/builtins/edit-apply.d.ts.map +1 -1
- package/dist/tools/builtins/edit-apply.js +24 -3
- package/dist/tools/builtins/edit-apply.js.map +1 -1
- package/dist/tools/builtins/edit.d.ts +31 -17
- package/dist/tools/builtins/edit.d.ts.map +1 -1
- package/dist/tools/builtins/edit.js +92 -27
- package/dist/tools/builtins/edit.js.map +1 -1
- package/dist/tools/builtins/glob.js +1 -1
- package/dist/tools/builtins/glob.js.map +1 -1
- package/dist/tools/builtins/grep.js +1 -1
- package/dist/tools/builtins/grep.js.map +1 -1
- package/dist/tools/builtins/notebook-guard.d.ts +19 -0
- package/dist/tools/builtins/notebook-guard.d.ts.map +1 -0
- package/dist/tools/builtins/notebook-guard.js +75 -0
- package/dist/tools/builtins/notebook-guard.js.map +1 -0
- package/dist/tools/builtins/read-render.d.ts.map +1 -1
- package/dist/tools/builtins/read-render.js +8 -2
- package/dist/tools/builtins/read-render.js.map +1 -1
- package/dist/tools/builtins/write-file.d.ts.map +1 -1
- package/dist/tools/builtins/write-file.js +14 -1
- package/dist/tools/builtins/write-file.js.map +1 -1
- package/dist/tools/coordinator/agent.d.ts.map +1 -1
- package/dist/tools/coordinator/agent.js +25 -6
- package/dist/tools/coordinator/agent.js.map +1 -1
- package/dist/tools/coordinator/index.d.ts.map +1 -1
- package/dist/tools/coordinator/index.js +6 -1
- package/dist/tools/coordinator/index.js.map +1 -1
- package/dist/tools/coordinator/outcome-record.d.ts +22 -0
- package/dist/tools/coordinator/outcome-record.d.ts.map +1 -0
- package/dist/tools/coordinator/outcome-record.js +91 -0
- package/dist/tools/coordinator/outcome-record.js.map +1 -0
- package/dist/tools/defineTool.d.ts +2 -0
- package/dist/tools/defineTool.d.ts.map +1 -1
- package/dist/tools/defineTool.js +1 -0
- package/dist/tools/defineTool.js.map +1 -1
- package/dist/tools/file-read-tracker.d.ts +23 -2
- package/dist/tools/file-read-tracker.d.ts.map +1 -1
- package/dist/tools/file-read-tracker.js +37 -2
- package/dist/tools/file-read-tracker.js.map +1 -1
- package/dist/tools/untrusted-envelope.d.ts +6 -0
- package/dist/tools/untrusted-envelope.d.ts.map +1 -1
- package/dist/tools/untrusted-envelope.js +22 -1
- package/dist/tools/untrusted-envelope.js.map +1 -1
- package/dist/toolsets/manager.d.ts.map +1 -1
- package/dist/toolsets/manager.js +3 -4
- package/dist/toolsets/manager.js.map +1 -1
- package/dist/toolsets/validation-issues.d.ts +27 -0
- package/dist/toolsets/validation-issues.d.ts.map +1 -0
- package/dist/toolsets/validation-issues.js +83 -0
- package/dist/toolsets/validation-issues.js.map +1 -0
- package/dist/turn/memory-recall.d.ts +13 -0
- package/dist/turn/memory-recall.d.ts.map +1 -1
- package/dist/turn/memory-recall.js +18 -0
- package/dist/turn/memory-recall.js.map +1 -1
- package/dist/types/agent/base.d.ts +19 -0
- package/dist/types/agent/base.d.ts.map +1 -1
- package/dist/types/agent/query.d.ts +2 -0
- package/dist/types/agent/query.d.ts.map +1 -1
- package/dist/types/common/index.d.ts +17 -0
- package/dist/types/common/index.d.ts.map +1 -1
- package/dist/types/common/index.js +8 -1
- package/dist/types/common/index.js.map +1 -1
- package/dist/types/hitl/index.d.ts +22 -0
- package/dist/types/hitl/index.d.ts.map +1 -1
- package/dist/types/hitl/index.js.map +1 -1
- package/dist/types/session/config.d.ts +20 -0
- package/dist/types/session/config.d.ts.map +1 -1
- package/dist/types/session/events.d.ts +3 -3
- package/dist/types/session/events.d.ts.map +1 -1
- package/dist/types/session/index.d.ts +1 -0
- package/dist/types/session/index.d.ts.map +1 -1
- package/dist/types/session/index.js +1 -0
- package/dist/types/session/index.js.map +1 -1
- package/dist/types/session/outcome.d.ts +95 -0
- package/dist/types/session/outcome.d.ts.map +1 -0
- package/dist/types/session/outcome.js +45 -0
- package/dist/types/session/outcome.js.map +1 -0
- package/dist/types/session/prepare-step.d.ts +2 -0
- package/dist/types/session/prepare-step.d.ts.map +1 -1
- package/dist/types/session/records.d.ts +167 -2
- package/dist/types/session/records.d.ts.map +1 -1
- package/dist/types/session/records.js +42 -0
- package/dist/types/session/records.js.map +1 -1
- package/dist/types/session/turn.d.ts +18 -0
- package/dist/types/session/turn.d.ts.map +1 -1
- package/dist/types/session/turn.js.map +1 -1
- package/dist/types/tool/index.d.ts +30 -0
- package/dist/types/tool/index.d.ts.map +1 -1
- package/dist/types/tool/index.js.map +1 -1
- package/package.json +1 -1
- package/src/agents/QueryAgent.ts +5 -0
- package/src/agents/examples/SupervisorAgent.ts +1 -0
- package/src/agents/explore.ts +2 -1
- package/src/compaction/index.ts +1 -0
- package/src/compaction/model-summary.ts +2 -2
- package/src/compaction/summary.ts +30 -2
- package/src/config/runtime.ts +15 -0
- package/src/execution/workflow/runtime.ts +4 -0
- package/src/execution/workflow/types.ts +9 -0
- package/src/manager/agent/child-session.ts +36 -0
- package/src/manager/agent/lifecycle.ts +9 -0
- package/src/manager/session/turn-recorder.ts +62 -3
- package/src/pricing/rates.source.json +1 -1
- package/src/prompt/coding-agent-doctrine.ts +5 -3
- package/src/provider/fallback.ts +6 -1
- package/src/provider/retry.ts +51 -0
- package/src/public-runtime.ts +10 -1
- package/src/public-tools.ts +6 -1
- package/src/runtime/query/auto-review/classifier.ts +21 -5
- package/src/runtime/query/auto-review/prompt.ts +2 -0
- package/src/runtime/query/auto-review/script-evidence.ts +618 -0
- package/src/runtime/query/checkpoint.ts +53 -1
- package/src/runtime/query/executor.ts +159 -19
- package/src/runtime/query/finalize-turn.ts +8 -1
- package/src/runtime/query/index.ts +19 -0
- package/src/runtime/query/iteration/index.ts +167 -14
- package/src/runtime/query/iteration/phases/compaction.ts +53 -3
- package/src/runtime/query/iteration/phases/context.ts +6 -0
- package/src/runtime/query/iteration/stream-turn.ts +3 -3
- package/src/runtime/query/prelude-lease.ts +19 -0
- package/src/runtime/query/prepare-turn.ts +13 -4
- package/src/runtime/query/review-policy.ts +33 -0
- package/src/runtime/query/tooling.ts +3 -0
- package/src/runtime/query/turn/completion-audit.ts +76 -5
- package/src/runtime/query/turn/outcome-ledger.ts +633 -0
- package/src/sandbox/glob-segment.ts +48 -24
- package/src/store/session-log/index.ts +7 -0
- package/src/store/session-log/turn-outcome.ts +84 -0
- package/src/tools/builtins/bash.ts +1 -1
- package/src/tools/builtins/edit-apply.ts +26 -4
- package/src/tools/builtins/edit.ts +106 -41
- package/src/tools/builtins/glob.ts +1 -1
- package/src/tools/builtins/grep.ts +1 -1
- package/src/tools/builtins/notebook-guard.ts +76 -0
- package/src/tools/builtins/read-render.ts +12 -2
- package/src/tools/builtins/write-file.ts +12 -1
- package/src/tools/coordinator/agent.ts +31 -7
- package/src/tools/coordinator/index.ts +6 -1
- package/src/tools/coordinator/outcome-record.ts +93 -0
- package/src/tools/defineTool.ts +3 -0
- package/src/tools/file-read-tracker.ts +51 -2
- package/src/tools/untrusted-envelope.ts +23 -1
- package/src/toolsets/manager.ts +6 -10
- package/src/toolsets/validation-issues.ts +97 -0
- package/src/turn/memory-recall.ts +28 -0
- package/src/types/agent/base.ts +20 -0
- package/src/types/agent/query.ts +2 -0
- package/src/types/common/index.ts +25 -1
- package/src/types/hitl/index.ts +27 -0
- package/src/types/session/config.ts +20 -0
- package/src/types/session/events.ts +3 -3
- package/src/types/session/index.ts +1 -0
- package/src/types/session/outcome.ts +139 -0
- package/src/types/session/prepare-step.ts +2 -0
- package/src/types/session/records.ts +67 -1
- package/src/types/session/turn.ts +19 -0
- package/src/types/tool/index.ts +30 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The turn's outcome record: what changed in the workspace, what was run to
|
|
3
|
+
* check it, and whether the last word of each check was said about the
|
|
4
|
+
* workspace as the turn left it.
|
|
5
|
+
*
|
|
6
|
+
* It is derived from the turn's own tool calls and, when the host supplies a
|
|
7
|
+
* change detector, from the workspace itself. Never from what the model says.
|
|
8
|
+
* Nothing here blocks or prolongs a turn; the completion audit keeps its own
|
|
9
|
+
* rule. Every uncertainty is reported as uncertainty: a check whose exit status
|
|
10
|
+
* something else may have decided is `masked`, not `passed`; a detector that
|
|
11
|
+
* cannot answer is `null`, never a match.
|
|
12
|
+
*/
|
|
13
|
+
import { type TurnOutcome, type TurnOutcomeCheckResult, type TurnOutcomeConfig } from '../../../types/session/outcome.js';
|
|
14
|
+
import type { ToolFacts } from '../../../types/tool/index.js';
|
|
15
|
+
/** A finished tool call as the ledger sees it. */
|
|
16
|
+
export interface OutcomeCall {
|
|
17
|
+
readonly toolName: string;
|
|
18
|
+
/** The call's parsed input, when it had one. */
|
|
19
|
+
readonly input?: unknown;
|
|
20
|
+
readonly isError?: boolean;
|
|
21
|
+
/** A hook stopped it before it ran. */
|
|
22
|
+
readonly skipped?: boolean;
|
|
23
|
+
/** The tool declared this call read-only (or it only keeps the task list). */
|
|
24
|
+
readonly readOnly?: boolean;
|
|
25
|
+
/** What the call did, as typed fields. Absent when a hook replaced the output. */
|
|
26
|
+
readonly facts?: ToolFacts;
|
|
27
|
+
}
|
|
28
|
+
/** A call about to run: enough to tell whether it could change the workspace. */
|
|
29
|
+
export interface PendingOutcomeCall {
|
|
30
|
+
readonly toolName: string;
|
|
31
|
+
readonly input?: unknown;
|
|
32
|
+
readonly readOnly?: boolean;
|
|
33
|
+
}
|
|
34
|
+
declare const SNAPSHOT_VERSION = 1;
|
|
35
|
+
/** The part of the ledger a checkpoint keeps, so a resumed turn does not forget what came before it. */
|
|
36
|
+
export interface OutcomeLedgerSnapshot {
|
|
37
|
+
readonly v: typeof SNAPSHOT_VERSION;
|
|
38
|
+
readonly started: boolean;
|
|
39
|
+
readonly startFingerprint: string | null;
|
|
40
|
+
readonly paths: readonly string[];
|
|
41
|
+
readonly writes: number;
|
|
42
|
+
/** Absent in a snapshot written before it existed. */
|
|
43
|
+
readonly unseen?: boolean;
|
|
44
|
+
/** Absent in a snapshot written before it existed. */
|
|
45
|
+
readonly pathless?: number;
|
|
46
|
+
readonly checks: readonly {
|
|
47
|
+
readonly key: string;
|
|
48
|
+
readonly command: string;
|
|
49
|
+
readonly step: number;
|
|
50
|
+
readonly result: TurnOutcomeCheckResult;
|
|
51
|
+
readonly exitCode?: number;
|
|
52
|
+
readonly afterWrite: boolean;
|
|
53
|
+
readonly afterUnknown: boolean;
|
|
54
|
+
readonly fingerprint: string | null;
|
|
55
|
+
}[];
|
|
56
|
+
}
|
|
57
|
+
export declare class OutcomeLedger {
|
|
58
|
+
private readonly config;
|
|
59
|
+
private started;
|
|
60
|
+
private startFingerprint;
|
|
61
|
+
private readonly paths;
|
|
62
|
+
private extraPaths;
|
|
63
|
+
/** Successful writes whose path could not be read; each is a change that cannot be named. */
|
|
64
|
+
private pathless;
|
|
65
|
+
/** A call ran that could change the workspace and is not a write tool: only the detector could say whether it did. */
|
|
66
|
+
private couldHaveChangedUnseen;
|
|
67
|
+
private writes;
|
|
68
|
+
private readonly checks;
|
|
69
|
+
private unrecorded;
|
|
70
|
+
private settled;
|
|
71
|
+
constructor(config?: TurnOutcomeConfig);
|
|
72
|
+
/** False once the record is known to be missing what came before a resume. */
|
|
73
|
+
get recorded(): boolean;
|
|
74
|
+
/**
|
|
75
|
+
* The turn resumed from a checkpoint that carries no ledger. What happened
|
|
76
|
+
* before it is unknown, and a record that began as `unchanged` would be a
|
|
77
|
+
* lie, so the turn reports no outcome at all.
|
|
78
|
+
*/
|
|
79
|
+
markUnrecorded(): void;
|
|
80
|
+
/** Take back what a checkpoint kept. A snapshot that does not parse leaves the turn unrecorded. */
|
|
81
|
+
restore(snapshot: unknown): void;
|
|
82
|
+
/** What a checkpoint should keep. */
|
|
83
|
+
snapshot(): OutcomeLedgerSnapshot;
|
|
84
|
+
/**
|
|
85
|
+
* Whether the call could change the workspace. Anything not provably
|
|
86
|
+
* read-only counts: a tool that does not say, a shell line that is not on
|
|
87
|
+
* the read-only list.
|
|
88
|
+
*/
|
|
89
|
+
private couldChange;
|
|
90
|
+
private detect;
|
|
91
|
+
/**
|
|
92
|
+
* Called with a step's calls before any of them runs. The first call of the
|
|
93
|
+
* turn that could change the workspace takes the starting fingerprint; a turn
|
|
94
|
+
* of provably read-only calls never asks the detector anything.
|
|
95
|
+
*/
|
|
96
|
+
before(calls: readonly PendingOutcomeCall[]): Promise<void>;
|
|
97
|
+
/** Feed the calls of one finished step, in order. */
|
|
98
|
+
observe(step: number, calls: readonly OutcomeCall[]): Promise<void>;
|
|
99
|
+
private afterEveryCheck;
|
|
100
|
+
private recordWrite;
|
|
101
|
+
private recordCheck;
|
|
102
|
+
private judge;
|
|
103
|
+
/** The outcome, once, when the turn ends. `undefined` when the record is not whole. */
|
|
104
|
+
finish(): Promise<TurnOutcome | undefined>;
|
|
105
|
+
/**
|
|
106
|
+
* The record as it stands, on the calls basis, without asking the detector
|
|
107
|
+
* and without settling: the turn goes on and `finish` still answers once.
|
|
108
|
+
*/
|
|
109
|
+
peek(): TurnOutcome | undefined;
|
|
110
|
+
private compute;
|
|
111
|
+
}
|
|
112
|
+
export {};
|
|
113
|
+
//# sourceMappingURL=outcome-ledger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outcome-ledger.d.ts","sourceRoot":"","sources":["../../../../src/runtime/query/turn/outcome-ledger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The turn's outcome record: what changed in the workspace, what was run to
|
|
3
|
+
* check it, and whether the last word of each check was said about the
|
|
4
|
+
* workspace as the turn left it.
|
|
5
|
+
*
|
|
6
|
+
* It is derived from the turn's own tool calls and, when the host supplies a
|
|
7
|
+
* change detector, from the workspace itself. Never from what the model says.
|
|
8
|
+
* Nothing here blocks or prolongs a turn; the completion audit keeps its own
|
|
9
|
+
* rule. Every uncertainty is reported as uncertainty: a check whose exit status
|
|
10
|
+
* something else may have decided is `masked`, not `passed`; a detector that
|
|
11
|
+
* cannot answer is `null`, never a match.
|
|
12
|
+
*/
|
|
13
|
+
import { isReadOnlyCommandLine } from '../../../authorization/readonly-commands.js';
|
|
14
|
+
import { lexShellCommandLine } from '../../../authorization/shell-lexer.js';
|
|
15
|
+
import { OUTCOME_DEFAULT_DETECT_TIMEOUT_MS, OUTCOME_MAX_CHECKS, OUTCOME_MAX_COMMAND_CHARS, OUTCOME_MAX_PATHS, OUTCOME_MAX_PATH_CHARS, } from '../../../types/session/outcome.js';
|
|
16
|
+
import { WRITING_TOOLS, isVerificationCommand } from './completion-audit.js';
|
|
17
|
+
/** Beyond these the ledger counts but no longer remembers. */
|
|
18
|
+
const MAX_REMEMBERED_PATHS = 1000;
|
|
19
|
+
const MAX_REMEMBERED_CHECKS = 200;
|
|
20
|
+
/** Programs that print or search text. A line that only names a check to one of them does not run it. */
|
|
21
|
+
const NOT_A_RUNNER = new Set([
|
|
22
|
+
'echo',
|
|
23
|
+
'printf',
|
|
24
|
+
'cat',
|
|
25
|
+
'grep',
|
|
26
|
+
'rg',
|
|
27
|
+
'sed',
|
|
28
|
+
'awk',
|
|
29
|
+
'head',
|
|
30
|
+
'tail',
|
|
31
|
+
'less',
|
|
32
|
+
'more',
|
|
33
|
+
'man',
|
|
34
|
+
'which',
|
|
35
|
+
'type',
|
|
36
|
+
'command',
|
|
37
|
+
'ls',
|
|
38
|
+
'find',
|
|
39
|
+
'wc',
|
|
40
|
+
'true',
|
|
41
|
+
'false',
|
|
42
|
+
'test',
|
|
43
|
+
'[',
|
|
44
|
+
':',
|
|
45
|
+
]);
|
|
46
|
+
/** Package managers whose `install`-like subcommands name a check word without running it. */
|
|
47
|
+
const PACKAGE_MANAGERS = new Set([
|
|
48
|
+
'npm',
|
|
49
|
+
'pnpm',
|
|
50
|
+
'yarn',
|
|
51
|
+
'bun',
|
|
52
|
+
'pip',
|
|
53
|
+
'pip3',
|
|
54
|
+
'uv',
|
|
55
|
+
'poetry',
|
|
56
|
+
'cargo',
|
|
57
|
+
'composer',
|
|
58
|
+
'gem',
|
|
59
|
+
]);
|
|
60
|
+
const INSTALLING = new Set([
|
|
61
|
+
'install',
|
|
62
|
+
'add',
|
|
63
|
+
'i',
|
|
64
|
+
'ci',
|
|
65
|
+
'remove',
|
|
66
|
+
'rm',
|
|
67
|
+
'uninstall',
|
|
68
|
+
'update',
|
|
69
|
+
'upgrade',
|
|
70
|
+
]);
|
|
71
|
+
const HELP_OR_VERSION = new Set(['--help', '-h', '--version', '-V', '--list', '--listTests']);
|
|
72
|
+
/** `-v` is verbose to these, a version flag to the rest. */
|
|
73
|
+
const VERBOSE_V = new Set(['pytest', 'py.test', 'mocha', 'vitest', 'jest']);
|
|
74
|
+
const SNAPSHOT_VERSION = 1;
|
|
75
|
+
function clip(text, max) {
|
|
76
|
+
return text.length > max ? `${text.slice(0, max)}…` : text;
|
|
77
|
+
}
|
|
78
|
+
function normalise(command) {
|
|
79
|
+
return command.trim().replace(/\s+/g, ' ');
|
|
80
|
+
}
|
|
81
|
+
function commandOf(input) {
|
|
82
|
+
const command = input?.command;
|
|
83
|
+
return typeof command === 'string' ? command : undefined;
|
|
84
|
+
}
|
|
85
|
+
function pathOf(call) {
|
|
86
|
+
if (call.facts?.kind === 'edit' || call.facts?.kind === 'write') {
|
|
87
|
+
if (call.facts.path !== undefined)
|
|
88
|
+
return call.facts.path;
|
|
89
|
+
}
|
|
90
|
+
const input = call.input;
|
|
91
|
+
for (const key of ['path', 'file_path', 'filePath', 'notebook_path']) {
|
|
92
|
+
const value = input?.[key];
|
|
93
|
+
if (typeof value === 'string' && value !== '')
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Whether the line's exit status is its last check's own: every command of the
|
|
100
|
+
* line is a plain command, joined to the others only by `&&`, with nothing
|
|
101
|
+
* before the first, and nothing after the last but a closing `;`. A pipe, a
|
|
102
|
+
* `;` between commands, `||`, a trailing `&`, a group, a nested shell or
|
|
103
|
+
* anything the lexer cannot read to the end lets something else decide the
|
|
104
|
+
* status, and the answer is no. `a && b` exits 0 only when both did.
|
|
105
|
+
*
|
|
106
|
+
* The bash tool does not set `pipefail`, so `pnpm test | tail` exits with
|
|
107
|
+
* `tail`'s status.
|
|
108
|
+
*/
|
|
109
|
+
function exitStatusIsOwn(line) {
|
|
110
|
+
const lexed = lexShellCommandLine(line);
|
|
111
|
+
if (!lexed.complete || lexed.opaque || lexed.commands.length === 0)
|
|
112
|
+
return false;
|
|
113
|
+
if (lexed.compoundWords.length > 0)
|
|
114
|
+
return false;
|
|
115
|
+
let cursor = 0;
|
|
116
|
+
for (const [index, command] of lexed.commands.entries()) {
|
|
117
|
+
if (command.origin !== 'line' || command.depth !== 0)
|
|
118
|
+
return false;
|
|
119
|
+
const at = line.indexOf(command.text, cursor);
|
|
120
|
+
if (at < 0)
|
|
121
|
+
return false;
|
|
122
|
+
const gap = line.slice(cursor, at).trim();
|
|
123
|
+
if (index === 0 ? gap !== '' : gap !== '&&')
|
|
124
|
+
return false;
|
|
125
|
+
cursor = at + command.text.length;
|
|
126
|
+
}
|
|
127
|
+
const tail = line.slice(cursor).trim();
|
|
128
|
+
return tail === '' || tail === ';';
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Whether a simple command, as its words, only installs, removes, or asks for
|
|
132
|
+
* help or a version. Such a command names a check word (`lint-staged`,
|
|
133
|
+
* `eslint --version`) and establishes nothing about the work.
|
|
134
|
+
*/
|
|
135
|
+
function establishesNothing(values) {
|
|
136
|
+
const program = (values[0] ?? '').split('/').pop() ?? '';
|
|
137
|
+
const args = values.slice(1);
|
|
138
|
+
if (args.some((word) => HELP_OR_VERSION.has(word)))
|
|
139
|
+
return true;
|
|
140
|
+
if (args.includes('-v') && !VERBOSE_V.has(program))
|
|
141
|
+
return true;
|
|
142
|
+
if (PACKAGE_MANAGERS.has(program) && args.slice(0, 3).some((word) => INSTALLING.has(word))) {
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
function isNamedCheck(text, hostIsCheck) {
|
|
148
|
+
if (isVerificationCommand(text))
|
|
149
|
+
return true;
|
|
150
|
+
try {
|
|
151
|
+
return hostIsCheck?.(text) === true;
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Whether the line holds a command that could change the workspace and is not
|
|
159
|
+
* itself a check: `pnpm test && sed -i s/a/b/ f`. A line the lexer cannot read
|
|
160
|
+
* to the end counts, because what it runs is not known.
|
|
161
|
+
*/
|
|
162
|
+
function changesBeyondChecks(line, hostIsCheck) {
|
|
163
|
+
const lexed = lexShellCommandLine(line);
|
|
164
|
+
if (!lexed.complete || lexed.opaque)
|
|
165
|
+
return true;
|
|
166
|
+
for (const command of lexed.commands) {
|
|
167
|
+
const values = command.words.slice(command.assignments).map((word) => word.value);
|
|
168
|
+
const program = (values[0] ?? '').split('/').pop() ?? '';
|
|
169
|
+
const isCheck = !NOT_A_RUNNER.has(program) &&
|
|
170
|
+
!establishesNothing(values) &&
|
|
171
|
+
isNamedCheck(command.text, hostIsCheck);
|
|
172
|
+
if (isCheck)
|
|
173
|
+
continue;
|
|
174
|
+
if (isReadOnlyCommandLine(command.text))
|
|
175
|
+
continue;
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
/** Whether some simple command of the line is a recognised check that is not merely named. */
|
|
181
|
+
function runsRecognisedCheck(line, hostIsCheck) {
|
|
182
|
+
const lexed = lexShellCommandLine(line);
|
|
183
|
+
if (!lexed.complete || lexed.opaque) {
|
|
184
|
+
return !establishesNothing(line.trim().split(/\s+/)) && isNamedCheck(line, hostIsCheck);
|
|
185
|
+
}
|
|
186
|
+
for (const command of lexed.commands) {
|
|
187
|
+
const values = command.words.slice(command.assignments).map((word) => word.value);
|
|
188
|
+
const program = (values[0] ?? '').split('/').pop() ?? '';
|
|
189
|
+
if (NOT_A_RUNNER.has(program))
|
|
190
|
+
continue;
|
|
191
|
+
if (establishesNothing(values))
|
|
192
|
+
continue;
|
|
193
|
+
if (isNamedCheck(command.text, hostIsCheck))
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
export class OutcomeLedger {
|
|
199
|
+
config;
|
|
200
|
+
started = false;
|
|
201
|
+
startFingerprint = null;
|
|
202
|
+
paths = new Set();
|
|
203
|
+
extraPaths = 0;
|
|
204
|
+
/** Successful writes whose path could not be read; each is a change that cannot be named. */
|
|
205
|
+
pathless = 0;
|
|
206
|
+
/** A call ran that could change the workspace and is not a write tool: only the detector could say whether it did. */
|
|
207
|
+
couldHaveChangedUnseen = false;
|
|
208
|
+
writes = 0;
|
|
209
|
+
checks = new Map();
|
|
210
|
+
unrecorded = false;
|
|
211
|
+
settled;
|
|
212
|
+
constructor(config = {}) {
|
|
213
|
+
this.config = config;
|
|
214
|
+
}
|
|
215
|
+
/** False once the record is known to be missing what came before a resume. */
|
|
216
|
+
get recorded() {
|
|
217
|
+
return !this.unrecorded;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* The turn resumed from a checkpoint that carries no ledger. What happened
|
|
221
|
+
* before it is unknown, and a record that began as `unchanged` would be a
|
|
222
|
+
* lie, so the turn reports no outcome at all.
|
|
223
|
+
*/
|
|
224
|
+
markUnrecorded() {
|
|
225
|
+
this.unrecorded = true;
|
|
226
|
+
}
|
|
227
|
+
/** Take back what a checkpoint kept. A snapshot that does not parse leaves the turn unrecorded. */
|
|
228
|
+
restore(snapshot) {
|
|
229
|
+
const parsed = parseSnapshot(snapshot);
|
|
230
|
+
if (parsed === undefined) {
|
|
231
|
+
this.unrecorded = true;
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
this.started = parsed.started;
|
|
235
|
+
this.startFingerprint = parsed.startFingerprint;
|
|
236
|
+
for (const path of parsed.paths)
|
|
237
|
+
this.paths.add(path);
|
|
238
|
+
this.writes = parsed.writes;
|
|
239
|
+
this.pathless = parsed.pathless ?? 0;
|
|
240
|
+
this.couldHaveChangedUnseen = parsed.unseen === true;
|
|
241
|
+
this.checks.clear();
|
|
242
|
+
for (const check of parsed.checks) {
|
|
243
|
+
this.checks.set(check.key, { ...check, fingerprint: check.fingerprint });
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/** What a checkpoint should keep. */
|
|
247
|
+
snapshot() {
|
|
248
|
+
return {
|
|
249
|
+
v: SNAPSHOT_VERSION,
|
|
250
|
+
started: this.started,
|
|
251
|
+
startFingerprint: this.startFingerprint,
|
|
252
|
+
paths: [...this.paths],
|
|
253
|
+
writes: this.writes,
|
|
254
|
+
unseen: this.couldHaveChangedUnseen,
|
|
255
|
+
pathless: this.pathless,
|
|
256
|
+
checks: [...this.checks.values()].map((check) => ({
|
|
257
|
+
key: check.key,
|
|
258
|
+
command: check.command,
|
|
259
|
+
step: check.step,
|
|
260
|
+
result: check.result,
|
|
261
|
+
...(check.exitCode !== undefined ? { exitCode: check.exitCode } : {}),
|
|
262
|
+
afterWrite: check.afterWrite,
|
|
263
|
+
afterUnknown: check.afterUnknown,
|
|
264
|
+
fingerprint: check.fingerprint ?? null,
|
|
265
|
+
})),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Whether the call could change the workspace. Anything not provably
|
|
270
|
+
* read-only counts: a tool that does not say, a shell line that is not on
|
|
271
|
+
* the read-only list.
|
|
272
|
+
*/
|
|
273
|
+
couldChange(call) {
|
|
274
|
+
if (call.readOnly === true)
|
|
275
|
+
return false;
|
|
276
|
+
if (call.toolName === 'bash') {
|
|
277
|
+
const command = commandOf(call.input);
|
|
278
|
+
if (command !== undefined && isReadOnlyCommandLine(command))
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
async detect() {
|
|
284
|
+
const detect = this.config.detectChanges;
|
|
285
|
+
if (detect === undefined)
|
|
286
|
+
return null;
|
|
287
|
+
const deadline = this.config.detectTimeoutMs ?? OUTCOME_DEFAULT_DETECT_TIMEOUT_MS;
|
|
288
|
+
let timer;
|
|
289
|
+
try {
|
|
290
|
+
const asked = Promise.resolve().then(detect);
|
|
291
|
+
// A detector that settles after its deadline must not surface later.
|
|
292
|
+
asked.catch(() => { });
|
|
293
|
+
const late = new Promise((resolve) => {
|
|
294
|
+
timer = setTimeout(() => resolve(null), deadline);
|
|
295
|
+
});
|
|
296
|
+
const answer = await Promise.race([asked, late]);
|
|
297
|
+
return typeof answer === 'string' ? answer : null;
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
finally {
|
|
303
|
+
if (timer !== undefined)
|
|
304
|
+
clearTimeout(timer);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Called with a step's calls before any of them runs. The first call of the
|
|
309
|
+
* turn that could change the workspace takes the starting fingerprint; a turn
|
|
310
|
+
* of provably read-only calls never asks the detector anything.
|
|
311
|
+
*/
|
|
312
|
+
async before(calls) {
|
|
313
|
+
if (this.started || this.config.detectChanges === undefined)
|
|
314
|
+
return;
|
|
315
|
+
if (!calls.some((call) => this.couldChange(call)))
|
|
316
|
+
return;
|
|
317
|
+
this.started = true;
|
|
318
|
+
this.startFingerprint = await this.detect();
|
|
319
|
+
}
|
|
320
|
+
/** Feed the calls of one finished step, in order. */
|
|
321
|
+
async observe(step, calls) {
|
|
322
|
+
// Checks that finished in this step with nothing after them that could
|
|
323
|
+
// change the workspace: the detector, asked once at the end of the step,
|
|
324
|
+
// answers for exactly the state they left.
|
|
325
|
+
let pending = [];
|
|
326
|
+
for (const call of calls) {
|
|
327
|
+
if (call.skipped === true)
|
|
328
|
+
continue;
|
|
329
|
+
const written = WRITING_TOOLS.has(call.toolName);
|
|
330
|
+
if (written && call.isError !== true) {
|
|
331
|
+
if (call.facts?.kind === 'write' && call.facts.operation === 'unchanged')
|
|
332
|
+
continue;
|
|
333
|
+
this.recordWrite(call);
|
|
334
|
+
this.afterEveryCheck('write');
|
|
335
|
+
for (const check of pending)
|
|
336
|
+
check.fingerprint = null;
|
|
337
|
+
pending = [];
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
if (written)
|
|
341
|
+
continue;
|
|
342
|
+
const command = call.toolName === 'bash' ? commandOf(call.input) : undefined;
|
|
343
|
+
if (command !== undefined && runsRecognisedCheck(command, this.config.isCheck)) {
|
|
344
|
+
this.afterEveryCheck('other');
|
|
345
|
+
for (const check of pending)
|
|
346
|
+
check.fingerprint = null;
|
|
347
|
+
pending = [];
|
|
348
|
+
const state = this.recordCheck(step, command, call);
|
|
349
|
+
// `pnpm test && sed -i ...`: the line changed something after the check.
|
|
350
|
+
if (changesBeyondChecks(command, this.config.isCheck)) {
|
|
351
|
+
state.afterUnknown = true;
|
|
352
|
+
this.couldHaveChangedUnseen = true;
|
|
353
|
+
}
|
|
354
|
+
pending.push(state);
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
if (this.couldChange(call)) {
|
|
358
|
+
this.couldHaveChangedUnseen = true;
|
|
359
|
+
this.afterEveryCheck('other');
|
|
360
|
+
for (const check of pending)
|
|
361
|
+
check.fingerprint = null;
|
|
362
|
+
pending = [];
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
if (pending.length > 0 && this.started && this.startFingerprint !== null) {
|
|
366
|
+
const fingerprint = await this.detect();
|
|
367
|
+
for (const check of pending)
|
|
368
|
+
check.fingerprint = fingerprint;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
afterEveryCheck(kind) {
|
|
372
|
+
for (const check of this.checks.values()) {
|
|
373
|
+
if (kind === 'write')
|
|
374
|
+
check.afterWrite = true;
|
|
375
|
+
else
|
|
376
|
+
check.afterUnknown = true;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
recordWrite(call) {
|
|
380
|
+
this.writes += 1;
|
|
381
|
+
const path = pathOf(call);
|
|
382
|
+
if (path === undefined) {
|
|
383
|
+
this.pathless += 1;
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const clipped = clip(path, OUTCOME_MAX_PATH_CHARS);
|
|
387
|
+
if (this.paths.has(clipped))
|
|
388
|
+
return;
|
|
389
|
+
if (this.paths.size >= MAX_REMEMBERED_PATHS) {
|
|
390
|
+
this.extraPaths += 1;
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
this.paths.add(clipped);
|
|
394
|
+
}
|
|
395
|
+
recordCheck(step, command, call) {
|
|
396
|
+
const key = normalise(command);
|
|
397
|
+
const { result, exitCode } = this.judge(command, call);
|
|
398
|
+
const state = {
|
|
399
|
+
key,
|
|
400
|
+
command: clip(key, OUTCOME_MAX_COMMAND_CHARS),
|
|
401
|
+
step,
|
|
402
|
+
result,
|
|
403
|
+
...(exitCode !== undefined ? { exitCode } : {}),
|
|
404
|
+
afterWrite: false,
|
|
405
|
+
afterUnknown: false,
|
|
406
|
+
// Not asked for yet: `undefined` until the step's detector call answers.
|
|
407
|
+
fingerprint: undefined,
|
|
408
|
+
};
|
|
409
|
+
// A failure stands until the same command passes. A later run that never
|
|
410
|
+
// settled (background, timed out) says nothing about the failure.
|
|
411
|
+
const earlier = this.checks.get(key);
|
|
412
|
+
if (earlier?.result === 'failed' && state.result !== 'passed')
|
|
413
|
+
return earlier;
|
|
414
|
+
// Latest run of each distinct command, ordered by when it last ran.
|
|
415
|
+
this.checks.delete(key);
|
|
416
|
+
this.checks.set(key, state);
|
|
417
|
+
if (this.checks.size > MAX_REMEMBERED_CHECKS) {
|
|
418
|
+
// A failing check is never the one forgotten.
|
|
419
|
+
for (const [candidate, held] of this.checks) {
|
|
420
|
+
if (held.result !== 'failed' && candidate !== key) {
|
|
421
|
+
this.checks.delete(candidate);
|
|
422
|
+
break;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return state;
|
|
427
|
+
}
|
|
428
|
+
judge(command, call) {
|
|
429
|
+
const facts = call.facts;
|
|
430
|
+
if (facts?.kind === 'failure')
|
|
431
|
+
return { result: 'failed' };
|
|
432
|
+
if (facts?.kind !== 'command') {
|
|
433
|
+
// No typed record of the run: a hook replaced the output. An error is
|
|
434
|
+
// still an error; anything else says nothing about the exit status.
|
|
435
|
+
return { result: call.isError === true ? 'failed' : 'unsettled' };
|
|
436
|
+
}
|
|
437
|
+
const exitCode = facts.exitCode;
|
|
438
|
+
const withCode = exitCode !== undefined ? { exitCode } : {};
|
|
439
|
+
if (facts.background === true || facts.continued === true || facts.timedOut === true) {
|
|
440
|
+
return { result: 'unsettled', ...withCode };
|
|
441
|
+
}
|
|
442
|
+
if (exitCode === undefined) {
|
|
443
|
+
return { result: call.isError === true ? 'failed' : 'unsettled' };
|
|
444
|
+
}
|
|
445
|
+
if (exitCode === 0) {
|
|
446
|
+
if (call.isError === true)
|
|
447
|
+
return { result: 'unsettled', exitCode };
|
|
448
|
+
return { result: exitStatusIsOwn(command) ? 'passed' : 'masked', exitCode };
|
|
449
|
+
}
|
|
450
|
+
if (!exitStatusIsOwn(command))
|
|
451
|
+
return { result: 'masked', exitCode };
|
|
452
|
+
// A non-zero exit the command's own answer (`grep` found nothing) is not a
|
|
453
|
+
// failure, and not a pass either.
|
|
454
|
+
if (facts.exitNote !== undefined && call.isError !== true) {
|
|
455
|
+
return { result: 'unsettled', exitCode };
|
|
456
|
+
}
|
|
457
|
+
return { result: 'failed', exitCode };
|
|
458
|
+
}
|
|
459
|
+
/** The outcome, once, when the turn ends. `undefined` when the record is not whole. */
|
|
460
|
+
async finish() {
|
|
461
|
+
if (this.unrecorded)
|
|
462
|
+
return undefined;
|
|
463
|
+
if (this.settled !== undefined)
|
|
464
|
+
return this.settled;
|
|
465
|
+
let endFingerprint = null;
|
|
466
|
+
if (this.started)
|
|
467
|
+
endFingerprint = await this.detect();
|
|
468
|
+
const outcome = this.compute(endFingerprint);
|
|
469
|
+
this.settled = outcome;
|
|
470
|
+
return outcome;
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* The record as it stands, on the calls basis, without asking the detector
|
|
474
|
+
* and without settling: the turn goes on and `finish` still answers once.
|
|
475
|
+
*/
|
|
476
|
+
peek() {
|
|
477
|
+
if (this.unrecorded)
|
|
478
|
+
return undefined;
|
|
479
|
+
return this.compute(null);
|
|
480
|
+
}
|
|
481
|
+
compute(endFingerprint) {
|
|
482
|
+
const workspace = this.started && this.startFingerprint !== null && endFingerprint !== null;
|
|
483
|
+
const moved = workspace && this.startFingerprint !== endFingerprint;
|
|
484
|
+
// A write tool that succeeded changed something, even a file the detector
|
|
485
|
+
// cannot see (an ignored one). Counting it is the cautious direction.
|
|
486
|
+
const changed = this.writes > 0 || moved;
|
|
487
|
+
// Without the workspace, a shell command or another tool that may have
|
|
488
|
+
// changed files cannot be ruled out, so "nothing changed" is not said.
|
|
489
|
+
const unseen = !workspace && this.couldHaveChangedUnseen;
|
|
490
|
+
const all = [...this.checks.values()];
|
|
491
|
+
const freshOf = (check) => {
|
|
492
|
+
if (check.afterWrite)
|
|
493
|
+
return false;
|
|
494
|
+
if (workspace && check.fingerprint !== undefined) {
|
|
495
|
+
return check.fingerprint === null ? undefined : check.fingerprint === endFingerprint;
|
|
496
|
+
}
|
|
497
|
+
return check.afterUnknown ? undefined : true;
|
|
498
|
+
};
|
|
499
|
+
const rows = all.map((check) => ({ check, fresh: freshOf(check) }));
|
|
500
|
+
let verdict;
|
|
501
|
+
const passes = rows.filter((row) => row.check.result === 'passed');
|
|
502
|
+
if (rows.some((row) => row.check.result === 'failed'))
|
|
503
|
+
verdict = 'failing';
|
|
504
|
+
else if (!changed)
|
|
505
|
+
verdict = unseen ? 'unknown' : 'unchanged';
|
|
506
|
+
else if (rows.length === 0)
|
|
507
|
+
verdict = 'unchecked';
|
|
508
|
+
else if (passes.some((row) => row.fresh === true))
|
|
509
|
+
verdict = 'checked';
|
|
510
|
+
else if (passes.length > 0 && passes.every((row) => row.fresh === false))
|
|
511
|
+
verdict = 'stale';
|
|
512
|
+
else
|
|
513
|
+
verdict = 'inconclusive';
|
|
514
|
+
// A write tool whose path could not be read still changed something.
|
|
515
|
+
const total = this.paths.size + this.extraPaths + this.pathless;
|
|
516
|
+
const kept = rows.slice(-OUTCOME_MAX_CHECKS);
|
|
517
|
+
const checks = kept.map(({ check, fresh }) => ({
|
|
518
|
+
command: check.command,
|
|
519
|
+
step: check.step,
|
|
520
|
+
result: check.result,
|
|
521
|
+
...(check.exitCode !== undefined ? { exitCode: check.exitCode } : {}),
|
|
522
|
+
...(fresh !== undefined ? { fresh } : {}),
|
|
523
|
+
}));
|
|
524
|
+
return {
|
|
525
|
+
verdict,
|
|
526
|
+
basis: workspace ? 'workspace' : 'calls',
|
|
527
|
+
changes: {
|
|
528
|
+
total,
|
|
529
|
+
paths: [...this.paths].slice(0, OUTCOME_MAX_PATHS),
|
|
530
|
+
outsideKnownTools: moved && this.writes === 0,
|
|
531
|
+
},
|
|
532
|
+
checks,
|
|
533
|
+
...(rows.length > OUTCOME_MAX_CHECKS ? { checksTotal: rows.length } : {}),
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
function parseSnapshot(value) {
|
|
538
|
+
if (typeof value !== 'object' || value === null)
|
|
539
|
+
return undefined;
|
|
540
|
+
const snap = value;
|
|
541
|
+
if (snap.v !== SNAPSHOT_VERSION || typeof snap.started !== 'boolean')
|
|
542
|
+
return undefined;
|
|
543
|
+
const start = snap.startFingerprint;
|
|
544
|
+
if (start !== null && typeof start !== 'string')
|
|
545
|
+
return undefined;
|
|
546
|
+
if (!Array.isArray(snap.paths) || !snap.paths.every((p) => typeof p === 'string'))
|
|
547
|
+
return undefined;
|
|
548
|
+
if (typeof snap.writes !== 'number' || !Number.isSafeInteger(snap.writes) || snap.writes < 0) {
|
|
549
|
+
return undefined;
|
|
550
|
+
}
|
|
551
|
+
if (snap.unseen !== undefined && typeof snap.unseen !== 'boolean')
|
|
552
|
+
return undefined;
|
|
553
|
+
if (snap.pathless !== undefined &&
|
|
554
|
+
(typeof snap.pathless !== 'number' || !Number.isSafeInteger(snap.pathless) || snap.pathless < 0)) {
|
|
555
|
+
return undefined;
|
|
556
|
+
}
|
|
557
|
+
if (!Array.isArray(snap.checks))
|
|
558
|
+
return undefined;
|
|
559
|
+
const results = new Set(['passed', 'failed', 'masked', 'unsettled']);
|
|
560
|
+
for (const raw of snap.checks) {
|
|
561
|
+
if (typeof raw !== 'object' || raw === null)
|
|
562
|
+
return undefined;
|
|
563
|
+
const c = raw;
|
|
564
|
+
if (typeof c.key !== 'string' ||
|
|
565
|
+
typeof c.command !== 'string' ||
|
|
566
|
+
typeof c.step !== 'number' ||
|
|
567
|
+
typeof c.result !== 'string' ||
|
|
568
|
+
!results.has(c.result) ||
|
|
569
|
+
(c.exitCode !== undefined && typeof c.exitCode !== 'number') ||
|
|
570
|
+
typeof c.afterWrite !== 'boolean' ||
|
|
571
|
+
typeof c.afterUnknown !== 'boolean' ||
|
|
572
|
+
(c.fingerprint !== null && typeof c.fingerprint !== 'string')) {
|
|
573
|
+
return undefined;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
return snap;
|
|
577
|
+
}
|
|
578
|
+
//# sourceMappingURL=outcome-ledger.js.map
|