sortie-dogs 0.9.4 → 0.9.6
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/README.md +1 -1
- package/dist/asset-version.d.ts +1 -1
- package/dist/asset-version.js +1 -1
- package/dist/core/goal-bound.d.ts +6 -0
- package/dist/core/goal-bound.js +27 -2
- package/dist/core/goal-report.d.ts +18 -0
- package/dist/core/goal-report.js +21 -0
- package/dist/core/run-flight-ledger.d.ts +5 -0
- package/dist/core/run-flight-ledger.js +17 -1
- package/dist/core/scope-lease-registry.js +7 -4
- package/dist/plugin/gate.d.ts +1 -1
- package/dist/plugin/gate.js +15 -3
- package/dist/plugin/index.js +84 -29
- package/dist/plugin/run-metrics.d.ts +10 -2
- package/dist/plugin/run-metrics.js +61 -11
- package/dist/plugin/sortie-career.d.ts +55 -0
- package/dist/plugin/sortie-career.js +144 -0
- package/dist/plugin/sortie-debrief.d.ts +54 -0
- package/dist/plugin/sortie-debrief.js +251 -0
- package/dist/runtime-assets.d.ts +18 -12
- package/dist/runtime-assets.js +9 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ Requirements: Node.js 22.6 or newer, npm, and OpenCode.
|
|
|
24
24
|
|
|
25
25
|
Guides: [日本語](docs/guide-ja.md) · [简体中文](docs/guide-zh-CN.md) · [CLI testing](docs/cli-testing.md)
|
|
26
26
|
|
|
27
|
-
Release: [v0.9.
|
|
27
|
+
Release: [v0.9.6](https://github.com/zufall-upon/Sortie-dogs/releases/tag/v0.9.6)
|
|
28
28
|
|
|
29
29
|
## Quick start
|
|
30
30
|
|
package/dist/asset-version.d.ts
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
* Version of the installable runtime assets. Kept in its own module so the plugin can compare an
|
|
3
3
|
* installed project marker without importing every asset body.
|
|
4
4
|
*/
|
|
5
|
-
export declare const RUNTIME_ASSET_VERSION = "0.3.
|
|
5
|
+
export declare const RUNTIME_ASSET_VERSION = "0.3.81-mission-debrief-v1";
|
|
6
6
|
export type RuntimeAssetVersion = typeof RUNTIME_ASSET_VERSION;
|
package/dist/asset-version.js
CHANGED
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
* Version of the installable runtime assets. Kept in its own module so the plugin can compare an
|
|
3
3
|
* installed project marker without importing every asset body.
|
|
4
4
|
*/
|
|
5
|
-
export const RUNTIME_ASSET_VERSION = "0.3.
|
|
5
|
+
export const RUNTIME_ASSET_VERSION = "0.3.81-mission-debrief-v1";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ValidationOutcome } from "./validation-budget.js";
|
|
2
|
+
import type { GoalReport } from "./goal-report.js";
|
|
2
3
|
export declare const GOAL_BOUND_SCHEMA_VERSION: "0.1";
|
|
3
4
|
export declare const GOAL_BOUND_METADATA_KEY: "sortie-dogs.goal-bound/v1";
|
|
4
5
|
export type GoalDeliveryMode = "planning-only" | "mvp-first" | "repair-first" | "controlled-change";
|
|
@@ -112,6 +113,7 @@ export type GoalFlightEvent = (GoalEventBase & {
|
|
|
112
113
|
readonly delivery: GoalDeliveryMode;
|
|
113
114
|
readonly budget: GoalBudget;
|
|
114
115
|
readonly acceptance_contract: GoalAcceptanceContract | null;
|
|
116
|
+
readonly reset_no_progress?: boolean;
|
|
115
117
|
}) | (GoalEventBase & {
|
|
116
118
|
readonly kind: "ticket.issued";
|
|
117
119
|
readonly ticket_id: string;
|
|
@@ -177,6 +179,10 @@ export type GoalFlightEvent = (GoalEventBase & {
|
|
|
177
179
|
readonly kind: "goal.terminal";
|
|
178
180
|
readonly goal_id: string;
|
|
179
181
|
readonly receipt: GoalTerminalReceipt;
|
|
182
|
+
}) | (GoalEventBase & {
|
|
183
|
+
readonly kind: "goal.reported";
|
|
184
|
+
readonly goal_id: string;
|
|
185
|
+
readonly report: GoalReport;
|
|
180
186
|
});
|
|
181
187
|
export interface GoalFlightEventRecord {
|
|
182
188
|
readonly sequence: number;
|
package/dist/core/goal-bound.js
CHANGED
|
@@ -106,11 +106,17 @@ function addUnique(values, value) {
|
|
|
106
106
|
}
|
|
107
107
|
export function reduceGoalFlight(records) {
|
|
108
108
|
let state = initial();
|
|
109
|
+
let replanRevision = null;
|
|
110
|
+
let legacyResetRevision = false;
|
|
109
111
|
let previous = null;
|
|
110
112
|
for (const [index, record] of records.entries()) {
|
|
111
113
|
requireState(record.sequence === index + 1 && record.previous_hash === previous && HASH.test(record.event_hash), "invalid", "Goal ledger chain is malformed.");
|
|
112
114
|
previous = record.event_hash;
|
|
113
115
|
const event = record.event;
|
|
116
|
+
// Optional presentation records never participate in execution replay. Writers validate them;
|
|
117
|
+
// older/newer telemetry definitions must not prevent an existing goal from resuming.
|
|
118
|
+
if (event.kind === "goal.reported")
|
|
119
|
+
continue;
|
|
114
120
|
requireState(instant(event.at), "invalid", "Goal event timestamp is invalid.");
|
|
115
121
|
if (event.kind === "goal.accepted") {
|
|
116
122
|
requireState(state.goal_id === null || state.phase === "terminal", "transition", "An active goal already owns this root.");
|
|
@@ -122,6 +128,8 @@ export function reduceGoalFlight(records) {
|
|
|
122
128
|
latest_user_message_id: event.origin_user_message_id, selected_agent: event.selected_agent,
|
|
123
129
|
delivery: event.delivery, budget: event.budget, acceptance_contract: event.acceptance_contract, phase: "active",
|
|
124
130
|
session_ids: [event.origin_session_id] };
|
|
131
|
+
replanRevision = null;
|
|
132
|
+
legacyResetRevision = false;
|
|
125
133
|
continue;
|
|
126
134
|
}
|
|
127
135
|
requireState(state.goal_id !== null && event.goal_id === state.goal_id, "transition", "Goal identity mismatch.");
|
|
@@ -134,13 +142,22 @@ export function reduceGoalFlight(records) {
|
|
|
134
142
|
else if (event.kind === "goal.revised") {
|
|
135
143
|
requireState(state.phase !== "terminal" && event.revision === state.revision + 1 && event.scope_epoch === state.scope_epoch + 1 &&
|
|
136
144
|
HASH.test(event.acceptance_fingerprint) && event.budget.max_units >= state.consumed_units &&
|
|
137
|
-
|
|
145
|
+
event.budget.max_units >= state.validation_budget.consumed &&
|
|
146
|
+
validAcceptanceContract(event.acceptance_contract) &&
|
|
147
|
+
(event.reset_no_progress === undefined || typeof event.reset_no_progress === "boolean"), "transition", "Scope revision is stale or resets consumed budget.");
|
|
138
148
|
state = { ...state, revision: event.revision, scope_epoch: event.scope_epoch,
|
|
139
149
|
acceptance_fingerprint: event.acceptance_fingerprint, latest_user_message_id: event.origin_user_message_id,
|
|
140
150
|
selected_agent: event.selected_agent, delivery: event.delivery, budget: event.budget,
|
|
151
|
+
validation_budget: { ...state.validation_budget,
|
|
152
|
+
limit: state.validation_budget.limit === null ? null : event.budget.max_units },
|
|
141
153
|
acceptance_contract: event.acceptance_contract,
|
|
142
154
|
session_ids: addUnique(state.session_ids, event.session_id), phase: "active", stop_reason: null,
|
|
143
|
-
tickets: []
|
|
155
|
+
receipt: null, tickets: [], ...(event.reset_no_progress === true
|
|
156
|
+
? { no_progress_results: 0, replan_required: false, replan_used: false }
|
|
157
|
+
: {}) };
|
|
158
|
+
if (event.reset_no_progress === true)
|
|
159
|
+
replanRevision = null;
|
|
160
|
+
legacyResetRevision = event.reset_no_progress === undefined;
|
|
144
161
|
}
|
|
145
162
|
else if (event.kind === "ticket.issued") {
|
|
146
163
|
requireState(state.phase === "active" && event.revision === state.revision && event.scope_epoch === state.scope_epoch &&
|
|
@@ -158,6 +175,13 @@ export function reduceGoalFlight(records) {
|
|
|
158
175
|
session_ids: addUnique(state.session_ids, event.session_id) };
|
|
159
176
|
}
|
|
160
177
|
else if (event.kind === "dispatch.reserved") {
|
|
178
|
+
// A short-lived pre-marker runtime reset no-progress on revision but persisted no flag.
|
|
179
|
+
// A later accepted dispatch proves that reset; older revisions followed by an explicit
|
|
180
|
+
// replan retain their original semantics.
|
|
181
|
+
if (legacyResetRevision && state.replan_required && state.replan_used && replanRevision !== null && state.revision > replanRevision) {
|
|
182
|
+
state = { ...state, no_progress_results: 0, replan_required: false, replan_used: false };
|
|
183
|
+
replanRevision = null;
|
|
184
|
+
}
|
|
161
185
|
requireState(state.phase === "active" && !state.replan_required, "transition", "Dispatch requires terminal handling or one bounded replan.");
|
|
162
186
|
requireState(state.budget !== null && state.consumed_units + state.outstanding_reservations.length < state.budget.max_units, "budget", "Goal unit budget exhausted.");
|
|
163
187
|
requireState(state.budget.time_ms === null || (state.consumed_time_ms !== null && state.consumed_time_ms < state.budget.time_ms), "budget", "Goal time budget is exhausted or usage is unknown.");
|
|
@@ -215,6 +239,7 @@ export function reduceGoalFlight(records) {
|
|
|
215
239
|
else if (event.kind === "goal.replanned") {
|
|
216
240
|
requireState(state.phase === "active" && state.replan_required && !state.replan_used && event.revision === state.revision, "transition", "Bounded replan is unavailable.");
|
|
217
241
|
state = { ...state, replan_used: true, replan_required: false, no_progress_results: 0 };
|
|
242
|
+
replanRevision = event.revision;
|
|
218
243
|
}
|
|
219
244
|
else if (event.kind === "goal.terminal") {
|
|
220
245
|
const allAccepted = state.acceptance_contract !== null && state.acceptance_contract.criteria.every(({ criterion_id }) => state.satisfied_criteria.includes(criterion_id));
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Optional, bounded telemetry retained with the existing terminal goal ledger, never an authority input. */
|
|
2
|
+
export interface GoalReport {
|
|
3
|
+
readonly definition: "pre-terminal-host-tokens/v1";
|
|
4
|
+
readonly terminal_key: string;
|
|
5
|
+
readonly tokens: number | null;
|
|
6
|
+
readonly models: readonly {
|
|
7
|
+
readonly model: string;
|
|
8
|
+
readonly tokens: number;
|
|
9
|
+
}[] | null;
|
|
10
|
+
readonly first_pass_eligible: boolean;
|
|
11
|
+
readonly traits: readonly ("pack-tactics" | "recovery" | "clean-sweep")[];
|
|
12
|
+
readonly overlap?: {
|
|
13
|
+
readonly definition: "worker-span-union/v1";
|
|
14
|
+
readonly worker_ms: number;
|
|
15
|
+
readonly wall_ms: number;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export declare function validGoalReport(value: unknown): value is GoalReport;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function validGoalReport(value) {
|
|
2
|
+
if (value === null || typeof value !== "object")
|
|
3
|
+
return false;
|
|
4
|
+
const item = value;
|
|
5
|
+
const amount = (n) => typeof n === "number" && Number.isSafeInteger(n) && n >= 0;
|
|
6
|
+
const duration = (n) => typeof n === "number" && Number.isFinite(n) && n >= 0;
|
|
7
|
+
const overlap = item.overlap;
|
|
8
|
+
return Object.keys(item).every((key) => ["definition", "terminal_key", "tokens", "models", "first_pass_eligible", "traits", "overlap"].includes(key)) &&
|
|
9
|
+
item.definition === "pre-terminal-host-tokens/v1" && /^sha256:[a-f0-9]{64}$/u.test(item.terminal_key) &&
|
|
10
|
+
(item.tokens === null || amount(item.tokens)) && typeof item.first_pass_eligible === "boolean" &&
|
|
11
|
+
Array.isArray(item.traits) && item.traits.length <= 3 && new Set(item.traits).size === item.traits.length &&
|
|
12
|
+
item.traits.every((trait) => ["pack-tactics", "recovery", "clean-sweep"].includes(trait)) &&
|
|
13
|
+
(overlap === undefined || overlap !== null && typeof overlap === "object" &&
|
|
14
|
+
Object.keys(overlap).every((key) => ["definition", "worker_ms", "wall_ms"].includes(key)) && overlap.definition === "worker-span-union/v1" &&
|
|
15
|
+
duration(overlap.worker_ms) && duration(overlap.wall_ms) && overlap.worker_ms >= overlap.wall_ms) &&
|
|
16
|
+
(item.models === null || Array.isArray(item.models) && item.models.length <= 128 &&
|
|
17
|
+
new Set(item.models.map((entry) => entry?.model)).size === item.models.length &&
|
|
18
|
+
item.models.every((entry) => entry !== null && typeof entry === "object" && Object.keys(entry).every((key) => key === "model" || key === "tokens") &&
|
|
19
|
+
typeof entry.model === "string" && entry.model.length > 0 && entry.model.length <= 512 && amount(entry.tokens)) &&
|
|
20
|
+
item.tokens !== null && item.models.reduce((sum, entry) => sum + entry.tokens, 0) === item.tokens);
|
|
21
|
+
}
|
|
@@ -366,6 +366,11 @@ export declare class RunFlightLedger {
|
|
|
366
366
|
static open(filePath: string, evidence: RunFlightEvidenceAccess): Promise<RunFlightLedger>;
|
|
367
367
|
/** Root goal checkpoints share this owner and lock/write discipline without inventing a Git run. */
|
|
368
368
|
static openGoal(filePath: string): Promise<RunFlightLedger>;
|
|
369
|
+
/** Read-only Career inventory: validates one existing goal ledger without opening another store. */
|
|
370
|
+
static readGoalFile(filePath: string): Promise<{
|
|
371
|
+
readonly records: readonly GoalFlightEventRecord[];
|
|
372
|
+
readonly state: GoalFlightState;
|
|
373
|
+
}>;
|
|
369
374
|
read(): Promise<{
|
|
370
375
|
readonly records: readonly RunFlightEventRecord[];
|
|
371
376
|
readonly state: RunFlightState;
|
|
@@ -4,7 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { LUNA_FABRIC_MAX_ACTIVE } from "./luna-fabric-scheduler.js";
|
|
5
5
|
import { normalizeRelativePath } from "./path.js";
|
|
6
6
|
import { CHILD_TERMINAL_EVIDENCE_FIELDS, isChildTerminalIdentity, reconcileChildTerminal, sameChildTerminalIdentity } from "./child-terminal-reconciliation.js";
|
|
7
|
-
import { GOAL_BOUND_SCHEMA_VERSION, GoalBoundError, reduceGoalFlight } from "./goal-bound.js";
|
|
7
|
+
import { GOAL_BOUND_SCHEMA_VERSION, GoalBoundError, reduceGoalFlight, goalFingerprint } from "./goal-bound.js";
|
|
8
8
|
export const RUN_FLIGHT_LEDGER_SCHEMA_VERSION = "0.1";
|
|
9
9
|
export const MAX_RUN_FLIGHT_EVENTS = 2048;
|
|
10
10
|
export const MAX_RUN_FLIGHT_LEDGER_BYTES = 1024 * 1024;
|
|
@@ -657,6 +657,10 @@ export class RunFlightLedger {
|
|
|
657
657
|
await ledger.readGoal();
|
|
658
658
|
return ledger;
|
|
659
659
|
}
|
|
660
|
+
/** Read-only Career inventory: validates one existing goal ledger without opening another store. */
|
|
661
|
+
static async readGoalFile(filePath) {
|
|
662
|
+
return new RunFlightLedger(filePath, undefined, true).readGoal();
|
|
663
|
+
}
|
|
660
664
|
async read() {
|
|
661
665
|
if (this.#goalMode)
|
|
662
666
|
throw new RunFlightLedgerError("invalid", "Goal ledger requires readGoal().");
|
|
@@ -820,6 +824,18 @@ export class RunFlightLedger {
|
|
|
820
824
|
throw new RunFlightLedgerError("conflict", "Ledger lock remained busy.");
|
|
821
825
|
try {
|
|
822
826
|
const records = await this.#readGoalRecords();
|
|
827
|
+
if (event.kind === "goal.reported" && records.some(({ event: stored }) => stored.kind === "goal.reported" &&
|
|
828
|
+
stored.goal_id === event.goal_id && stored.report?.terminal_key === event.report.terminal_key)) {
|
|
829
|
+
return reduceGoalFlight(records);
|
|
830
|
+
}
|
|
831
|
+
if (event.kind === "goal.reported") {
|
|
832
|
+
const { validGoalReport } = await import("./goal-report.js");
|
|
833
|
+
const state = reduceGoalFlight(records);
|
|
834
|
+
if (state.receipt === null || event.goal_id !== state.goal_id || !validGoalReport(event.report) ||
|
|
835
|
+
event.report.terminal_key !== goalFingerprint(state.receipt)) {
|
|
836
|
+
throw new RunFlightLedgerError("invalid", "Report is not bound to the current terminal receipt.");
|
|
837
|
+
}
|
|
838
|
+
}
|
|
823
839
|
const sequence = records.length + 1;
|
|
824
840
|
const previousHash = records.at(-1)?.event_hash ?? null;
|
|
825
841
|
const record = { sequence, previous_hash: previousHash,
|
|
@@ -472,13 +472,16 @@ export class ScopeLeaseRegistry {
|
|
|
472
472
|
await rmdir(this.lockPath).catch(() => undefined);
|
|
473
473
|
return;
|
|
474
474
|
}
|
|
475
|
-
|
|
475
|
+
const owners = names.filter((name) => OWNER_FILE.test(name));
|
|
476
|
+
if (owners.length !== 1 || names.length > MAX_TEMP_CLEANUP + 1 ||
|
|
477
|
+
names.some((name) => name !== owners[0] && !TEMP_FILE.test(name)))
|
|
476
478
|
return;
|
|
477
|
-
const
|
|
479
|
+
const ownerName = owners[0];
|
|
480
|
+
const match = OWNER_FILE.exec(ownerName);
|
|
478
481
|
if (match === null || !UUID.test(match[1]))
|
|
479
482
|
return;
|
|
480
483
|
const owner = match[1];
|
|
481
|
-
const ownerPath = join(this.lockPath,
|
|
484
|
+
const ownerPath = join(this.lockPath, ownerName);
|
|
482
485
|
const before = await this.mutexSnapshot(ownerPath);
|
|
483
486
|
if (before === undefined || Date.now() - before.mtimeMs <= this.options.mutexStaleMs || before.content !== owner)
|
|
484
487
|
return;
|
|
@@ -490,7 +493,7 @@ export class ScopeLeaseRegistry {
|
|
|
490
493
|
catch {
|
|
491
494
|
return;
|
|
492
495
|
}
|
|
493
|
-
const movedOwnerPath = join(quarantine,
|
|
496
|
+
const movedOwnerPath = join(quarantine, ownerName);
|
|
494
497
|
const after = await this.mutexSnapshot(movedOwnerPath);
|
|
495
498
|
if (after === undefined || !this.sameMutexSnapshot(before, after)) {
|
|
496
499
|
// The whole-directory rename already fenced this owner. Keep its quarantine until a later
|
package/dist/plugin/gate.d.ts
CHANGED
|
@@ -69,5 +69,5 @@ export declare function canonicalManifestWriteScopes(project: ProjectPaths, mani
|
|
|
69
69
|
export declare function canonicalManifestReadScopes(project: ProjectPaths, manifest: OperationManifest): Promise<readonly string[]>;
|
|
70
70
|
export declare function writeScopesOverlap(left: readonly string[], right: readonly string[]): boolean;
|
|
71
71
|
export declare function createProjectPaths(rootCandidate: string): Promise<ProjectPaths>;
|
|
72
|
-
export declare function createWriteGate(project: ProjectPaths, value: unknown): Promise<WriteGate>;
|
|
72
|
+
export declare function createWriteGate(project: ProjectPaths, value: unknown, toolDirectory?: string): Promise<WriteGate>;
|
|
73
73
|
export {};
|
package/dist/plugin/gate.js
CHANGED
|
@@ -964,7 +964,7 @@ export async function createProjectPaths(rootCandidate) {
|
|
|
964
964
|
},
|
|
965
965
|
};
|
|
966
966
|
}
|
|
967
|
-
export async function createWriteGate(project, value) {
|
|
967
|
+
export async function createWriteGate(project, value, toolDirectory = project.root) {
|
|
968
968
|
const validated = validateOperationManifestSchema(value);
|
|
969
969
|
if (!validated.ok)
|
|
970
970
|
throw new WriteDeniedError("manifest-unavailable", "<unknown>");
|
|
@@ -1189,8 +1189,20 @@ export async function createWriteGate(project, value) {
|
|
|
1189
1189
|
}
|
|
1190
1190
|
throw new WriteDeniedError("path-required", "<missing-path>");
|
|
1191
1191
|
}
|
|
1192
|
-
|
|
1193
|
-
|
|
1192
|
+
// OpenCode resolves native file-tool paths against its instance directory, not the
|
|
1193
|
+
// manifest root. Check the same destination without rewriting the tool arguments.
|
|
1194
|
+
const nativeFileTool = /^(?:write|edit)(?:$|[_-])/iu.test(_input.tool) || /patch/iu.test(_input.tool);
|
|
1195
|
+
for (const path of extracted.paths) {
|
|
1196
|
+
if (nativeFileTool) {
|
|
1197
|
+
try {
|
|
1198
|
+
normalizeManifestPath(path);
|
|
1199
|
+
}
|
|
1200
|
+
catch (error) {
|
|
1201
|
+
throw new WriteDeniedError("project-boundary", path, { cause: error });
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
await checkPath(nativeFileTool ? resolve(toolDirectory, path) : path);
|
|
1205
|
+
}
|
|
1194
1206
|
if (extracted.gitCommit)
|
|
1195
1207
|
await checkCachedSet();
|
|
1196
1208
|
},
|
package/dist/plugin/index.js
CHANGED
|
@@ -45,7 +45,7 @@ import { TerminalRescueRuntime } from "../core/terminal-rescue-runtime.js";
|
|
|
45
45
|
import { OpenCodeTerminalRescueHost } from "./terminal-rescue-host.js";
|
|
46
46
|
import { AdaptiveRemediationRuntime, AdaptiveRunFlightLineage } from "../core/adaptive-remediation-runtime.js";
|
|
47
47
|
import { DEFAULT_ADAPTIVE_REMEDIATION_MODEL, GitAdaptiveRemediationHost, OpenCodeAdaptiveRemediationProvider } from "./adaptive-remediation-host.js";
|
|
48
|
-
import { collectRunMetrics, createSortieResult, insertRunMetrics, insertSortieResult, replaceDoneTerminalStatus, replaceTerminalStatus, sanitizeTerminalReport, terminalRunOutcome } from "./run-metrics.js";
|
|
48
|
+
import { collectRunMetrics, createSortieResult, createGoalReport, insertRunMetrics, insertSortieResult, replaceDoneTerminalStatus, replaceTerminalStatus, sanitizeTerminalReport, terminalRunOutcome } from "./run-metrics.js";
|
|
49
49
|
const INPUT_LIMITS = { config: 64 * 1024, manifest: 512 * 1024, handoff: 2 * 1024 * 1024, parallel: 512 * 1024 };
|
|
50
50
|
const INSPECTION_CACHE = { maximum: 256, ttlMilliseconds: 30 * 60 * 1000 };
|
|
51
51
|
const ACTIVE_SESSION_CACHE = { maximum: 256, ttlMilliseconds: 30 * 60 * 1000 };
|
|
@@ -1127,15 +1127,18 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1127
1127
|
const parallelAcceptanceContinuity = new Map();
|
|
1128
1128
|
const goalRootSessions = new Map();
|
|
1129
1129
|
const goalLedgers = new Map();
|
|
1130
|
+
const goalLedgerFiles = new Map();
|
|
1131
|
+
const goalLedgerDirectories = new Set();
|
|
1130
1132
|
const goalReservations = new Map();
|
|
1131
1133
|
const hostGoalExecutions = new Map();
|
|
1134
|
+
const goalValidationDefects = new Set();
|
|
1132
1135
|
const goalDeclarationAuthority = new Map();
|
|
1133
1136
|
const pendingRealGoalTurns = new Map();
|
|
1134
1137
|
const pendingGoalRecoveries = new Map();
|
|
1135
1138
|
const scheduledGoalRecoveries = new Map();
|
|
1136
1139
|
const globalConfig = await readOptionalGlobalConfig();
|
|
1137
1140
|
const sessionOperationMetrics = new Map();
|
|
1138
|
-
function appLogInfo(message, sessionID, extra) {
|
|
1141
|
+
function appLogInfo(message, sessionID, extra, level = "info") {
|
|
1139
1142
|
const app = input.client?.app;
|
|
1140
1143
|
const log = app?.log;
|
|
1141
1144
|
if (log === undefined)
|
|
@@ -1144,7 +1147,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1144
1147
|
const result = log.call(app, {
|
|
1145
1148
|
body: {
|
|
1146
1149
|
service: "sortie-dogs",
|
|
1147
|
-
level
|
|
1150
|
+
level,
|
|
1148
1151
|
message,
|
|
1149
1152
|
extra: { sessionID: sessionID.slice(0, 128), ...extra },
|
|
1150
1153
|
},
|
|
@@ -1242,15 +1245,22 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1242
1245
|
const key = createHash("sha256").update(root).digest("hex");
|
|
1243
1246
|
const projectRoot = resolve(input.worktree ?? input.directory);
|
|
1244
1247
|
const legacyPath = join(projectRoot, ".sortie-dogs", "run-flight", `${key}.json`);
|
|
1248
|
+
goalLedgerDirectories.add(dirname(legacyPath));
|
|
1245
1249
|
const opened = (async () => {
|
|
1250
|
+
const legacyExists = await stat(legacyPath).then((value) => value.isFile()).catch(() => false);
|
|
1251
|
+
const leaseRoot = legacyExists ? await durableScopeRoot(projectRoot).catch(() => undefined) : await durableScopeRoot(projectRoot);
|
|
1252
|
+
if (leaseRoot !== undefined)
|
|
1253
|
+
goalLedgerDirectories.add(join(dirname(leaseRoot), "run-flight"));
|
|
1246
1254
|
// Existing roots remain on their original owner so an in-flight pre-upgrade goal is not forked.
|
|
1247
|
-
if (
|
|
1255
|
+
if (legacyExists) {
|
|
1256
|
+
goalLedgerFiles.set(root, legacyPath);
|
|
1248
1257
|
return await RunFlightLedger.openGoal(legacyPath);
|
|
1249
1258
|
}
|
|
1250
|
-
const leaseRoot = await durableScopeRoot(projectRoot);
|
|
1251
1259
|
const filePath = leaseRoot === undefined
|
|
1252
1260
|
? legacyPath
|
|
1253
1261
|
: join(dirname(leaseRoot), "run-flight", `${key}.json`);
|
|
1262
|
+
goalLedgerFiles.set(root, filePath);
|
|
1263
|
+
goalLedgerDirectories.add(dirname(filePath));
|
|
1254
1264
|
return await RunFlightLedger.openGoal(filePath);
|
|
1255
1265
|
})();
|
|
1256
1266
|
goalLedgers.set(root, opened);
|
|
@@ -1362,8 +1372,12 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1362
1372
|
const unitID = authorization.taskID;
|
|
1363
1373
|
const unitBound = unitID !== undefined && goal.outstanding_reservations.some((reservation) => reservation.unit_id === unitID && reservation.session_id === identity.parentID);
|
|
1364
1374
|
const criteria = goal.acceptance_contract?.criteria.filter((criterion) => criterion.validation_command === rawCommand && criterion.expected_outcome === "pass") ?? [];
|
|
1375
|
+
const denyValidation = (reason) => {
|
|
1376
|
+
goalValidationDefects.add(toolInput.sessionID);
|
|
1377
|
+
return new Error(`SORTIE_VALIDATION_BUDGET_DENIED: ${reason}`);
|
|
1378
|
+
};
|
|
1365
1379
|
if (!unitBound)
|
|
1366
|
-
throw
|
|
1380
|
+
throw denyValidation("requirement-unbound");
|
|
1367
1381
|
// Exact generation and formatting checks may support acceptance without proving a criterion.
|
|
1368
1382
|
if (criteria.length === 0)
|
|
1369
1383
|
return;
|
|
@@ -1377,10 +1391,10 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1377
1391
|
reservation = await ledger.reserveValidation(request, goal.budget?.max_units ?? 1);
|
|
1378
1392
|
}
|
|
1379
1393
|
catch (error) {
|
|
1380
|
-
throw
|
|
1394
|
+
throw denyValidation(`authority-unavailable:${error instanceof Error ? error.name : "unknown"}`);
|
|
1381
1395
|
}
|
|
1382
1396
|
if (reservation.decision !== "ALLOW" || reservation.reservation_id === null) {
|
|
1383
|
-
throw
|
|
1397
|
+
throw denyValidation(reservation.reason);
|
|
1384
1398
|
}
|
|
1385
1399
|
validation = { ledger, request, reservation: reservation.reservation_id };
|
|
1386
1400
|
}
|
|
@@ -1615,11 +1629,12 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1615
1629
|
: "external_dependency";
|
|
1616
1630
|
receipt = await terminalGoal(sessionID, reason, "stopped").catch(() => undefined);
|
|
1617
1631
|
}
|
|
1618
|
-
|
|
1619
|
-
|
|
1632
|
+
const snapshot = receipt === undefined ? undefined : await goalLedger(sessionID).then((ledger) => ledger.readGoal()).catch(() => undefined);
|
|
1633
|
+
if (snapshot !== undefined)
|
|
1634
|
+
goal = snapshot.state;
|
|
1620
1635
|
if (receipt !== undefined)
|
|
1621
1636
|
rootAcceptanceContinuity.delete(sessionID);
|
|
1622
|
-
return { outcome, goal, receipt, delivery };
|
|
1637
|
+
return { outcome, goal, receipt, delivery, records: snapshot?.records };
|
|
1623
1638
|
}
|
|
1624
1639
|
function goalDeclarationContract(prompt) {
|
|
1625
1640
|
const lines = prompt.split(/\r?\n/u);
|
|
@@ -1768,27 +1783,29 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1768
1783
|
throw new HandoffDeniedError("contract-invalid", "<goal-declaration>", { defects: validated.defects });
|
|
1769
1784
|
}
|
|
1770
1785
|
const declaration = validated.declaration;
|
|
1786
|
+
const units = Number(handoffValue(entries, ["goal_budget_units"]));
|
|
1787
|
+
const maxUnits = Number.isSafeInteger(units) && units >= state.consumed_units && units > 0
|
|
1788
|
+
? units : state.budget?.max_units ?? 32;
|
|
1789
|
+
const declaredTime = Number(handoffValue(entries, ["goal_budget_time_ms"]));
|
|
1790
|
+
const declaredCost = Number(handoffValue(entries, ["goal_budget_cost_usd"]));
|
|
1791
|
+
const timeBudget = Number.isFinite(declaredTime) && declaredTime > 0 ? declaredTime : state.budget?.time_ms ?? null;
|
|
1792
|
+
const costBudget = Number.isFinite(declaredCost) && declaredCost > 0 ? declaredCost : state.budget?.cost_usd ?? null;
|
|
1793
|
+
const budgetChanged = maxUnits !== state.budget?.max_units || timeBudget !== state.budget?.time_ms ||
|
|
1794
|
+
costBudget !== state.budget?.cost_usd;
|
|
1771
1795
|
const authority = goalDeclarationAuthority.get(sessionID);
|
|
1772
1796
|
if (authority === undefined || authority !== state.latest_user_message_id) {
|
|
1773
|
-
if (declaration.fingerprint !== state.acceptance_fingerprint) {
|
|
1797
|
+
if (declaration.fingerprint !== state.acceptance_fingerprint || budgetChanged) {
|
|
1774
1798
|
throw new HandoffDeniedError("contract-invalid", "<goal-declaration>", { defects: [
|
|
1775
1799
|
contractDefect("contract", "/goal_acceptance_fingerprint", "goal_revision_unauthorized"),
|
|
1776
1800
|
] });
|
|
1777
1801
|
}
|
|
1778
1802
|
return state;
|
|
1779
1803
|
}
|
|
1780
|
-
if (declaration.fingerprint === state.acceptance_fingerprint &&
|
|
1804
|
+
if (!budgetChanged && declaration.fingerprint === state.acceptance_fingerprint &&
|
|
1781
1805
|
goalFingerprint(declaration.contract) === goalFingerprint(state.acceptance_contract)) {
|
|
1782
1806
|
goalDeclarationAuthority.delete(sessionID);
|
|
1783
1807
|
return state;
|
|
1784
1808
|
}
|
|
1785
|
-
const units = Number(handoffValue(entries, ["goal_budget_units"]));
|
|
1786
|
-
const maxUnits = Number.isSafeInteger(units) && units >= state.consumed_units && units > 0
|
|
1787
|
-
? units : state.budget?.max_units ?? 32;
|
|
1788
|
-
const declaredTime = Number(handoffValue(entries, ["goal_budget_time_ms"]));
|
|
1789
|
-
const declaredCost = Number(handoffValue(entries, ["goal_budget_cost_usd"]));
|
|
1790
|
-
const timeBudget = Number.isFinite(declaredTime) && declaredTime > 0 ? declaredTime : state.budget?.time_ms ?? null;
|
|
1791
|
-
const costBudget = Number.isFinite(declaredCost) && declaredCost > 0 ? declaredCost : state.budget?.cost_usd ?? null;
|
|
1792
1809
|
state = await ledger.appendGoal({ kind: "goal.revised", at: new Date().toISOString(), goal_id: state.goal_id,
|
|
1793
1810
|
revision: state.revision + 1, scope_epoch: state.scope_epoch + 1,
|
|
1794
1811
|
acceptance_fingerprint: declaration.fingerprint, origin_user_message_id: state.latest_user_message_id,
|
|
@@ -1796,7 +1813,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1796
1813
|
delivery: declaration.delivery, budget: { max_units: maxUnits,
|
|
1797
1814
|
time_ms: timeBudget, cost_usd: costBudget,
|
|
1798
1815
|
source: Number.isSafeInteger(units) ? "accepted-plan" : state.budget?.source ?? "policy-default" },
|
|
1799
|
-
acceptance_contract: declaration.contract });
|
|
1816
|
+
acceptance_contract: declaration.contract, reset_no_progress: true });
|
|
1800
1817
|
goalDeclarationAuthority.delete(sessionID);
|
|
1801
1818
|
return state;
|
|
1802
1819
|
}
|
|
@@ -1904,7 +1921,11 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1904
1921
|
output.status === "cancel" || output.status === "cancelled";
|
|
1905
1922
|
const hostBindingDefect = childSessionID !== undefined && [...(bindingDenials.get(reservation.root)?.values() ?? [])]
|
|
1906
1923
|
.some((candidateDenials) => [...candidateDenials.values()].includes(childSessionID));
|
|
1907
|
-
const
|
|
1924
|
+
const failedAcceptanceExecution = [...hostGoalExecutions.values()].some((execution) => execution.root === reservation.root && execution.sessionID === childSessionID &&
|
|
1925
|
+
execution.endedAt !== undefined && Date.parse(execution.startedAt) >= reservation.started - 1000 &&
|
|
1926
|
+
execution.outcome === "fail");
|
|
1927
|
+
const processDefect = !failedAcceptanceExecution && (childSessionID === undefined || hostBindingDefect ||
|
|
1928
|
+
goalValidationDefects.has(childSessionID));
|
|
1908
1929
|
const resultClass = progress ? "acceptance" : interrupted ? "interrupted" : processDefect ? "process-defect" : "acceptance";
|
|
1909
1930
|
await ledger.appendGoal({ kind: "unit.settled", at: new Date().toISOString(),
|
|
1910
1931
|
reservation_id: reservation.reservationID, receipt_id: goalFingerprint({ call_id: callID, output: outputText.slice(0, 2048) }),
|
|
@@ -1913,6 +1934,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1913
1934
|
progress_fingerprint: progress ? goalFingerprint(acceptedEvidence) : null,
|
|
1914
1935
|
evidence: acceptedEvidence, elapsed_ms: Math.max(0, Date.now() - reservation.started), cost_usd: null });
|
|
1915
1936
|
if (childSessionID !== undefined) {
|
|
1937
|
+
goalValidationDefects.delete(childSessionID);
|
|
1916
1938
|
for (const [executionCallID, execution] of hostGoalExecutions) {
|
|
1917
1939
|
if (execution.root === reservation.root && execution.sessionID === childSessionID)
|
|
1918
1940
|
hostGoalExecutions.delete(executionCallID);
|
|
@@ -2108,7 +2130,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
2108
2130
|
throw new WriteDeniedError("manifest-unavailable", "<unknown>");
|
|
2109
2131
|
loaded.manifest = validation.value;
|
|
2110
2132
|
loaded.manifestFingerprint = inspectionFingerprint(validation.value, undefined);
|
|
2111
|
-
loaded.gate = await createWriteGate(project, validation.value);
|
|
2133
|
+
loaded.gate = await createWriteGate(project, validation.value, input.directory);
|
|
2112
2134
|
bootstrapRequired = false;
|
|
2113
2135
|
bootstrapCompleted = true;
|
|
2114
2136
|
loadFailure = undefined;
|
|
@@ -4035,7 +4057,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
4035
4057
|
});
|
|
4036
4058
|
}
|
|
4037
4059
|
manifest = manifestValidation.value;
|
|
4038
|
-
authorizationGate = await createWriteGate(candidateProject, manifest);
|
|
4060
|
+
authorizationGate = await createWriteGate(candidateProject, manifest, input.directory);
|
|
4039
4061
|
inspectedProjectRoot = candidateProject.root;
|
|
4040
4062
|
}
|
|
4041
4063
|
catch (error) {
|
|
@@ -4118,7 +4140,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
4118
4140
|
const remedies = {
|
|
4119
4141
|
"session-inactive": {
|
|
4120
4142
|
recoverable: true,
|
|
4121
|
-
remedy: "Freshly redispatch
|
|
4143
|
+
remedy: "Freshly redispatch through dog-coordinator's admitted dog-worker Task, with prompt text containing role, project_root, source_manifest or operation_manifest, and acceptance or validation fields. A standalone build/fixer Task is not a registered Sortie worker; copying these fields, a bare resume, or a file read cannot activate it. Preserve explicit agent selection and resolve any existing goal stop before using the Sortie workflow; do not repeat the same standalone redispatch or reset its ledger to bypass a stop.",
|
|
4122
4144
|
},
|
|
4123
4145
|
"session-expired": {
|
|
4124
4146
|
recoverable: true,
|
|
@@ -4330,7 +4352,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
4330
4352
|
idempotent: true,
|
|
4331
4353
|
});
|
|
4332
4354
|
}
|
|
4333
|
-
const gate = await createWriteGate(candidate, validation.value);
|
|
4355
|
+
const gate = await createWriteGate(candidate, validation.value, input.directory);
|
|
4334
4356
|
const readScopes = await canonicalManifestReadScopes(candidate, validation.value);
|
|
4335
4357
|
const writeScopes = await canonicalManifestWriteScopes(candidate, validation.value);
|
|
4336
4358
|
// Keep conflict detection and registration in one JavaScript turn so competing binds fail closed.
|
|
@@ -4659,6 +4681,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
4659
4681
|
clearSessionLinks(sessionID);
|
|
4660
4682
|
}
|
|
4661
4683
|
function evictSession(sessionID) {
|
|
4684
|
+
goalValidationDefects.delete(sessionID);
|
|
4662
4685
|
activeSessions.delete(sessionID);
|
|
4663
4686
|
sessionOperationMetrics.delete(sessionID);
|
|
4664
4687
|
rootAcceptanceContinuity.delete(sessionID);
|
|
@@ -5354,13 +5377,33 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
5354
5377
|
startedAt: terminal.receipt.started_at,
|
|
5355
5378
|
endedAt: terminal.receipt.ended_at,
|
|
5356
5379
|
}).catch(() => undefined));
|
|
5357
|
-
|
|
5380
|
+
let sortieResult = terminal?.receipt === undefined || terminal.goal === undefined
|
|
5358
5381
|
? undefined
|
|
5359
|
-
: createSortieResult(terminal.receipt, terminal.goal, metrics, new Date().toISOString());
|
|
5382
|
+
: createSortieResult(terminal.receipt, terminal.goal, metrics, new Date().toISOString(), terminal.records);
|
|
5383
|
+
if (sortieResult !== undefined && terminal?.receipt !== undefined && terminal.records !== undefined) {
|
|
5384
|
+
try {
|
|
5385
|
+
const ledger = await goalLedger(textInput.sessionID);
|
|
5386
|
+
const report = createGoalReport(sortieResult, terminal.receipt);
|
|
5387
|
+
let records = terminal.records;
|
|
5388
|
+
if (!records.some(({ event }) => event.kind === "goal.reported" && event.report?.terminal_key === report.terminal_key)) {
|
|
5389
|
+
await ledger.appendGoal({ kind: "goal.reported", at: new Date().toISOString(), goal_id: terminal.receipt.goal_id, report });
|
|
5390
|
+
records = (await ledger.readGoal()).records;
|
|
5391
|
+
}
|
|
5392
|
+
const currentPath = goalLedgerFiles.get(goalRoot(textInput.sessionID));
|
|
5393
|
+
if (currentPath !== undefined) {
|
|
5394
|
+
const { collectCareer } = await import("./sortie-career.js");
|
|
5395
|
+
sortieResult = { ...sortieResult, career: await collectCareer([...goalLedgerDirectories], currentPath, records, (path) => RunFlightLedger.readGoalFile(path)) };
|
|
5396
|
+
}
|
|
5397
|
+
}
|
|
5398
|
+
catch {
|
|
5399
|
+
appLogInfo("run-metrics.career-unavailable", textInput.sessionID, { outcome: runOutcome }, "warn");
|
|
5400
|
+
}
|
|
5401
|
+
}
|
|
5360
5402
|
if (sortieResult !== undefined)
|
|
5361
5403
|
textOutput.text = insertSortieResult(textOutput.text, sortieResult);
|
|
5362
5404
|
else if (metrics !== undefined && runOutcome === "DONE")
|
|
5363
5405
|
textOutput.text = insertRunMetrics(textOutput.text, metrics);
|
|
5406
|
+
const { debrief: _debriefObservation, ...metricSummary } = metrics ?? {};
|
|
5364
5407
|
appLogInfo("run-metrics.snapshot", textInput.sessionID, {
|
|
5365
5408
|
available: metrics !== undefined,
|
|
5366
5409
|
outcome: runOutcome,
|
|
@@ -5371,7 +5414,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
5371
5414
|
resultProof: sortieResult.proof.overall,
|
|
5372
5415
|
accountingPhase: sortieResult.accounting_phase,
|
|
5373
5416
|
}),
|
|
5374
|
-
...
|
|
5417
|
+
...metricSummary,
|
|
5375
5418
|
...operationMetricsSnapshot(textInput.sessionID),
|
|
5376
5419
|
});
|
|
5377
5420
|
}
|
|
@@ -5705,6 +5748,16 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
5705
5748
|
*/
|
|
5706
5749
|
"tool.execute.after": async (toolInput, output) => {
|
|
5707
5750
|
await recordHostGoalEnd(toolInput, output);
|
|
5751
|
+
// A completed host question is a new user-interaction boundary, just like chat input.
|
|
5752
|
+
// It authorizes one subsequent typed declaration; it does not itself grant budget or clear a stop.
|
|
5753
|
+
if (toolInput.tool === "question" && toolInput.sessionID !== undefined &&
|
|
5754
|
+
isCoordinatorSession(toolInput.sessionID) && typeof output.output === "string" &&
|
|
5755
|
+
output.output.trim().length > 0 && output.status !== "error" && output.status !== "cancelled") {
|
|
5756
|
+
const goal = await currentGoal(toolInput.sessionID);
|
|
5757
|
+
if (goal.phase !== "terminal" && goal.latest_user_message_id !== null) {
|
|
5758
|
+
goalDeclarationAuthority.set(toolInput.sessionID, goal.latest_user_message_id);
|
|
5759
|
+
}
|
|
5760
|
+
}
|
|
5708
5761
|
diagnosisChildren.get(toolInput.sessionID ?? "")?.tools.delete(toolInput.callID ?? "");
|
|
5709
5762
|
const diagnosisCandidate = toolInput.tool === "task" ? diagnosisCalls.get(toolInput.callID ?? "") : undefined;
|
|
5710
5763
|
const diagnosis = diagnosisCandidate?.context.ownerRoot === toolInput.sessionID ? diagnosisCandidate : undefined;
|
|
@@ -6354,6 +6407,8 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
6354
6407
|
}
|
|
6355
6408
|
catch (error) {
|
|
6356
6409
|
activeState?.inFlightCalls.delete(toolInput.callID);
|
|
6410
|
+
if (error instanceof WriteDeniedError)
|
|
6411
|
+
goalValidationDefects.add(toolInput.sessionID);
|
|
6357
6412
|
if (!(error instanceof WriteDeniedError) || error.reason === "repeated-denial")
|
|
6358
6413
|
throw error;
|
|
6359
6414
|
const signature = denialSignature(toolInput, output, error.reason);
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import type { GoalFlightState, GoalTerminalReceipt } from "../core/goal-bound.js";
|
|
1
|
+
import type { GoalFlightState, GoalTerminalReceipt, GoalFlightEventRecord } from "../core/goal-bound.js";
|
|
2
|
+
import { type Debrief, type DebriefObservation } from "./sortie-debrief.ts";
|
|
3
|
+
import type { GoalReport } from "../core/goal-report.ts";
|
|
4
|
+
import { type SortieCareer } from "./sortie-career.ts";
|
|
2
5
|
export interface RunMetricsClient {
|
|
3
6
|
readonly session?: {
|
|
4
7
|
readonly get?: (request: {
|
|
@@ -44,6 +47,8 @@ export interface RunMetrics {
|
|
|
44
47
|
readonly sessions: number | undefined;
|
|
45
48
|
readonly cacheRatio: number | undefined;
|
|
46
49
|
readonly roles: Readonly<Record<string, RunRoleMetrics>> | undefined;
|
|
50
|
+
/** Optional for compatibility with older host snapshots. Collected in the existing history pass. */
|
|
51
|
+
readonly debrief?: DebriefObservation;
|
|
47
52
|
}
|
|
48
53
|
export interface RunRoleMetrics {
|
|
49
54
|
readonly tokens: number;
|
|
@@ -72,6 +77,8 @@ export interface SortieResult {
|
|
|
72
77
|
readonly result_id: readonly [goalID: string, terminalRevision: number];
|
|
73
78
|
readonly accounting_phase: "pre-terminal";
|
|
74
79
|
readonly as_of: string;
|
|
80
|
+
readonly debrief?: Debrief;
|
|
81
|
+
readonly career?: SortieCareer;
|
|
75
82
|
readonly mission: {
|
|
76
83
|
readonly status: "COMPLETED" | "INTERRUPTED" | "EXTERNAL_BLOCKER" | "USER_DECISION";
|
|
77
84
|
readonly stop_reason: GoalTerminalReceipt["stop_reason"];
|
|
@@ -102,7 +109,7 @@ export interface SortieResult {
|
|
|
102
109
|
}
|
|
103
110
|
type SortieGoalSnapshot = Pick<GoalFlightState, "acceptance_contract" | "consumed_time_ms" | "satisfied_criteria">;
|
|
104
111
|
/** Builds a pure terminal snapshot from the durable goal receipt and already-observed host metrics. */
|
|
105
|
-
export declare function createSortieResult(receipt: GoalTerminalReceipt, goal: SortieGoalSnapshot, metrics: RunMetrics | undefined, asOf?: string): SortieResult;
|
|
112
|
+
export declare function createSortieResult(receipt: GoalTerminalReceipt, goal: SortieGoalSnapshot, metrics: RunMetrics | undefined, asOf?: string, records?: readonly GoalFlightEventRecord[]): SortieResult;
|
|
106
113
|
export type RunTerminalOutcome = "DONE" | "INTERRUPTED" | "BLOCKED" | "NEED_DECISION";
|
|
107
114
|
export declare function collectRunMetrics(client: RunMetricsClient | undefined, rootSessionID: string, directory?: string, now?: number, window?: RunMetricsWindow): Promise<RunMetrics | undefined>;
|
|
108
115
|
export declare function formatSortieResult(result: SortieResult): string;
|
|
@@ -114,4 +121,5 @@ export declare function replaceDoneTerminalStatus(text: string, replacement: str
|
|
|
114
121
|
export declare function sanitizeTerminalReport(text: string): string;
|
|
115
122
|
export declare function insertRunMetrics(text: string, metrics: RunMetrics): string;
|
|
116
123
|
export declare function insertSortieResult(text: string, result: SortieResult): string;
|
|
124
|
+
export declare function createGoalReport(result: SortieResult, receipt: GoalTerminalReceipt): GoalReport;
|
|
117
125
|
export {};
|