@alisio/core 0.6.0 → 0.8.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/dist/application.d.ts +15 -0
- package/dist/application.js +38 -3
- package/dist/changelog/data.js +39 -0
- package/dist/commands/builtins.js +7 -0
- package/dist/config.d.ts +30 -0
- package/dist/config.js +32 -0
- package/dist/core/runner.d.ts +4 -0
- package/dist/core/runner.js +57 -1
- package/dist/eval/canonical.d.ts +14 -0
- package/dist/eval/canonical.js +40 -0
- package/dist/eval/cassette.d.ts +180 -0
- package/dist/eval/cassette.js +424 -0
- package/dist/eval/compare.d.ts +147 -0
- package/dist/eval/compare.js +436 -0
- package/dist/eval/engine.d.ts +145 -0
- package/dist/eval/engine.js +731 -0
- package/dist/eval/export.d.ts +40 -0
- package/dist/eval/export.js +90 -0
- package/dist/eval/fingerprint.d.ts +88 -0
- package/dist/eval/fingerprint.js +186 -0
- package/dist/eval/normalize.d.ts +68 -0
- package/dist/eval/normalize.js +138 -0
- package/dist/eval/paths.d.ts +11 -0
- package/dist/eval/paths.js +50 -0
- package/dist/eval/plan.d.ts +26 -0
- package/dist/eval/plan.js +44 -0
- package/dist/eval/report.d.ts +53 -0
- package/dist/eval/report.js +258 -0
- package/dist/eval/run-suite.d.ts +68 -0
- package/dist/eval/run-suite.js +94 -0
- package/dist/eval/secrets.d.ts +14 -0
- package/dist/eval/secrets.js +55 -0
- package/dist/eval/stats.d.ts +17 -0
- package/dist/eval/stats.js +50 -0
- package/dist/eval/suite.d.ts +20 -0
- package/dist/eval/suite.js +165 -0
- package/dist/eval/variants.d.ts +35 -0
- package/dist/eval/variants.js +137 -0
- package/dist/eval/verifiers.d.ts +49 -0
- package/dist/eval/verifiers.js +264 -0
- package/dist/forge/javascript/hosts/worker-host.d.ts +9 -0
- package/dist/forge/javascript/hosts/worker-host.js +16 -1
- package/dist/forge/kernel.js +3 -2
- package/dist/goal/command.d.ts +7 -2
- package/dist/goal/command.js +10 -2
- package/dist/goal/format.js +2 -0
- package/dist/goal/machine.d.ts +50 -3
- package/dist/goal/machine.js +80 -2
- package/dist/goal/service.d.ts +22 -2
- package/dist/goal/service.js +94 -7
- package/dist/goal/store.js +22 -3
- package/dist/index.d.ts +18 -2
- package/dist/index.js +18 -2
- package/dist/plan/criteria.d.ts +30 -0
- package/dist/plan/criteria.js +256 -0
- package/dist/plan/exit-plan.d.ts +8 -0
- package/dist/plan/exit-plan.js +91 -9
- package/dist/plan/manifest.d.ts +4 -2
- package/dist/plan/manifest.js +6 -1
- package/dist/plan/state.d.ts +13 -2
- package/dist/plan/state.js +7 -1
- package/dist/plugins/host.d.ts +7 -0
- package/dist/plugins/host.js +15 -0
- package/dist/proof/command.d.ts +24 -0
- package/dist/proof/command.js +109 -0
- package/dist/proof/coverage.d.ts +40 -0
- package/dist/proof/coverage.js +102 -0
- package/dist/proof/divergence.d.ts +24 -0
- package/dist/proof/divergence.js +120 -0
- package/dist/proof/export.d.ts +65 -0
- package/dist/proof/export.js +144 -0
- package/dist/proof/index.d.ts +12 -0
- package/dist/proof/index.js +12 -0
- package/dist/proof/ledger.d.ts +38 -0
- package/dist/proof/ledger.js +57 -0
- package/dist/proof/plugin-verifiers.d.ts +23 -0
- package/dist/proof/plugin-verifiers.js +39 -0
- package/dist/proof/recorder.d.ts +69 -0
- package/dist/proof/recorder.js +347 -0
- package/dist/proof/scope.d.ts +12 -0
- package/dist/proof/scope.js +121 -0
- package/dist/proof/store.d.ts +52 -0
- package/dist/proof/store.js +192 -0
- package/dist/proof/tree.d.ts +6 -0
- package/dist/proof/tree.js +78 -0
- package/dist/proof/types.d.ts +21 -0
- package/dist/proof/types.js +1 -0
- package/dist/proof/verifiers.d.ts +46 -0
- package/dist/proof/verifiers.js +176 -0
- package/dist/proof/verify.d.ts +36 -0
- package/dist/proof/verify.js +171 -0
- package/dist/runtime/ids.d.ts +21 -0
- package/dist/runtime/ids.js +41 -1
- package/dist/runtime/store.js +63 -2
- package/dist/tools/goal.d.ts +5 -1
- package/dist/tools/goal.js +35 -4
- package/package.json +2 -2
package/dist/goal/service.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { GoalEvidence, GoalInfo, GoalWaiting } from "@alisio/sdk";
|
|
1
|
+
import type { GoalEvidence, GoalInfo, GoalWaiting, ProofLedger } from "@alisio/sdk";
|
|
2
2
|
import { type GoalBlocker, type GoalHostState } from "./blockers.ts";
|
|
3
|
-
import { type GoalLimits, type GoalLog, type GoalRecord, type GoalRunOutcome } from "./machine.ts";
|
|
3
|
+
import { type GoalLimits, type GoalLog, type GoalRecord, type GoalRunOutcome, type GoalUnmet } from "./machine.ts";
|
|
4
4
|
import { type GoalPrompt } from "./prompts.ts";
|
|
5
5
|
import { type GoalExpectation, type GoalStore } from "./store.ts";
|
|
6
6
|
/** `config.goal`, as the service reads it (live: a settings change applies to the next call). */
|
|
@@ -58,6 +58,13 @@ export interface GoalServiceOptions {
|
|
|
58
58
|
onChange?: (sessionId: string, goal: GoalRecord | undefined) => void;
|
|
59
59
|
/** Breaker observations (counts only; never the objective or the reply). */
|
|
60
60
|
onLog?: (sessionId: string, log: GoalLog) => void;
|
|
61
|
+
/**
|
|
62
|
+
* Where the Proof ledger comes from (`ProofStore` fits). Without it criteria do not exist for
|
|
63
|
+
* the goal: completion behaves as in 0.7.x and a declared `evidenceId` is dropped.
|
|
64
|
+
*/
|
|
65
|
+
proof?: {
|
|
66
|
+
ledger(sessionId: string): ProofLedger;
|
|
67
|
+
};
|
|
61
68
|
}
|
|
62
69
|
export declare class GoalService {
|
|
63
70
|
private readonly options;
|
|
@@ -93,6 +100,10 @@ export declare class GoalService {
|
|
|
93
100
|
next(sessionId: string, view: GoalHostView): GoalDecision;
|
|
94
101
|
/** The claimed continuation could not start: give the claim back (the next call may retry). */
|
|
95
102
|
abandon(sessionId: string, requestId: string, kind: GoalPrompt["kind"]): void;
|
|
103
|
+
/** Runs whose `complete` the ledger refused, per goal (memory only: a restart pauses goals). */
|
|
104
|
+
private readonly refusals;
|
|
105
|
+
/** Records a refused completion of `runId`; true once enough different runs were refused. */
|
|
106
|
+
private refusedRuns;
|
|
96
107
|
/** `update_goal` (the model): the only transitions the model can cause. */
|
|
97
108
|
report(sessionId: string, runId: string | undefined, report: {
|
|
98
109
|
status: "complete" | "blocked";
|
|
@@ -107,7 +118,16 @@ export declare class GoalService {
|
|
|
107
118
|
ok: false;
|
|
108
119
|
code: GoalErrorCode;
|
|
109
120
|
message: string;
|
|
121
|
+
unmet?: GoalUnmet[];
|
|
110
122
|
};
|
|
123
|
+
/**
|
|
124
|
+
* The USER completes the goal over unmet required criteria (O6). A separate method on purpose:
|
|
125
|
+
* `report` is the model's only door and has no way to carry an override, so the model cannot
|
|
126
|
+
* reach this path whatever it sends. Hosts call it from a user action only (Phase 5 surfaces).
|
|
127
|
+
*/
|
|
128
|
+
overrideComplete(sessionId: string, input: {
|
|
129
|
+
reason: string;
|
|
130
|
+
}, expect?: GoalExpectation): GoalOutcome;
|
|
111
131
|
/** Startup: goals whose driver process is gone pause with reason `restart`. */
|
|
112
132
|
recover(): GoalRecord[];
|
|
113
133
|
}
|
package/dist/goal/service.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import { randomUUID } from "node:crypto";
|
|
19
19
|
import { goalBlocker, waitingOf } from "./blockers.js";
|
|
20
20
|
import { objectiveError } from "./command.js";
|
|
21
|
-
import { checkCaps, DEFAULT_GOAL_LIMITS, modelBlocked, modelComplete, newGoal, settleRun, toGoalInfo, userBudget, userEdit, userPause, userResume, } from "./machine.js";
|
|
21
|
+
import { COMPLETION_REFUSAL_LIMIT, checkCaps, criteriaForGoal, DEFAULT_GOAL_LIMITS, modelBlocked, modelComplete, newGoal, pauseForRefusedCompletions, settleRun, toGoalInfo, userBudget, userEdit, userOverrideComplete, userPause, userResume, } from "./machine.js";
|
|
22
22
|
import { buildGoalPrompt } from "./prompts.js";
|
|
23
23
|
export const settingsToLimits = (settings) => ({
|
|
24
24
|
maxTurns: settings.maxTurns,
|
|
@@ -27,6 +27,24 @@ export const settingsToLimits = (settings) => ({
|
|
|
27
27
|
noToolTurnsLimit: settings.noToolTurnsLimit,
|
|
28
28
|
blockedRepeats: settings.blockedRepeats,
|
|
29
29
|
});
|
|
30
|
+
/**
|
|
31
|
+
* The declared evidence with every `evidenceId` that does not resolve to captured evidence of the
|
|
32
|
+
* session removed (such a declaration is free text and backs nothing), and the ids that resolved.
|
|
33
|
+
*/
|
|
34
|
+
function linkEvidence(evidence, ledger) {
|
|
35
|
+
const known = new Set(ledger?.evidence.map((e) => e.evidenceId));
|
|
36
|
+
const linked = [];
|
|
37
|
+
const out = evidence.map((item) => {
|
|
38
|
+
const { evidenceId, ...rest } = item;
|
|
39
|
+
if (evidenceId === undefined)
|
|
40
|
+
return item;
|
|
41
|
+
if (!known.has(evidenceId))
|
|
42
|
+
return rest;
|
|
43
|
+
linked.push(evidenceId);
|
|
44
|
+
return item;
|
|
45
|
+
});
|
|
46
|
+
return { evidence: out, linked };
|
|
47
|
+
}
|
|
30
48
|
export class GoalService {
|
|
31
49
|
options;
|
|
32
50
|
store;
|
|
@@ -109,6 +127,9 @@ export class GoalService {
|
|
|
109
127
|
if (!this.enabled)
|
|
110
128
|
return { ok: false, code: "disabled", message: "Goals are turned off (goal.enabled)." };
|
|
111
129
|
const limits = this.limits();
|
|
130
|
+
const current = this.store.get(sessionId);
|
|
131
|
+
if (current)
|
|
132
|
+
this.refusals.delete(current.goalId);
|
|
112
133
|
return this.act(sessionId, expect, (goal) => userResume(goal, this.clock(), limits, this.pid));
|
|
113
134
|
}
|
|
114
135
|
edit(sessionId, objective, expect) {
|
|
@@ -232,17 +253,49 @@ export class GoalService {
|
|
|
232
253
|
if (result.ok)
|
|
233
254
|
this.emit(sessionId, result.goal);
|
|
234
255
|
}
|
|
256
|
+
/** Runs whose `complete` the ledger refused, per goal (memory only: a restart pauses goals). */
|
|
257
|
+
refusals = new Map();
|
|
258
|
+
/** Records a refused completion of `runId`; true once enough different runs were refused. */
|
|
259
|
+
refusedRuns(goalId, runId) {
|
|
260
|
+
const runs = this.refusals.get(goalId) ?? new Set();
|
|
261
|
+
runs.add(runId);
|
|
262
|
+
this.refusals.set(goalId, runs);
|
|
263
|
+
if (runs.size < COMPLETION_REFUSAL_LIMIT)
|
|
264
|
+
return false;
|
|
265
|
+
this.refusals.delete(goalId);
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
235
268
|
/** `update_goal` (the model): the only transitions the model can cause. */
|
|
236
269
|
report(sessionId, runId, report) {
|
|
237
270
|
const limits = this.limits();
|
|
238
271
|
const outcome = this.store.update(sessionId, (goal) => {
|
|
272
|
+
// The ledger is read inside this transaction, so the verdict and the write see one state.
|
|
273
|
+
// Only a completion, or a declaration that links evidence, needs it (D7: no extra reads).
|
|
274
|
+
const needsLedger = report.status === "complete" || report.evidence.some((e) => e.evidenceId !== undefined);
|
|
275
|
+
const ledger = needsLedger ? this.options.proof?.ledger(sessionId) : undefined;
|
|
276
|
+
const linked = linkEvidence(report.evidence, ledger);
|
|
239
277
|
if (report.status === "complete") {
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
278
|
+
const proof = ledger
|
|
279
|
+
? { criteria: criteriaForGoal(ledger, goal.goalId), linkedEvidence: linked.linked }
|
|
280
|
+
: undefined;
|
|
281
|
+
const result = modelComplete(goal, this.clock(), { summary: report.summary, evidence: linked.evidence }, proof);
|
|
282
|
+
if (result.ok)
|
|
283
|
+
return { goal: result.goal, value: { ok: true, stopped: true, needed: undefined } };
|
|
284
|
+
// A refusal over the ledger counts per run; the third run that tries pauses the goal.
|
|
285
|
+
if (result.unmet?.length && runId && this.refusedRuns(goal.goalId, runId)) {
|
|
286
|
+
return {
|
|
287
|
+
goal: pauseForRefusedCompletions(goal, this.clock()),
|
|
288
|
+
value: {
|
|
289
|
+
ok: false,
|
|
290
|
+
message: `${result.message} The goal is paused: completion was refused in ${COMPLETION_REFUSAL_LIMIT} different runs. The user decides: resume, or complete it with an override.`,
|
|
291
|
+
unmet: result.unmet,
|
|
292
|
+
paused: true,
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
return { value: { ok: false, message: result.message, unmet: result.unmet } };
|
|
244
297
|
}
|
|
245
|
-
const result = modelBlocked(goal, this.clock(), { summary: report.summary, evidence:
|
|
298
|
+
const result = modelBlocked(goal, this.clock(), { summary: report.summary, evidence: linked.evidence, runId }, limits);
|
|
246
299
|
return result.ok
|
|
247
300
|
? {
|
|
248
301
|
goal: result.goal.goal,
|
|
@@ -253,8 +306,15 @@ export class GoalService {
|
|
|
253
306
|
if (!outcome.ok)
|
|
254
307
|
return { ok: false, code: outcome.code, message: outcome.message };
|
|
255
308
|
const value = outcome.value;
|
|
309
|
+
if (!value.ok && value.paused)
|
|
310
|
+
this.emit(sessionId, outcome.goal);
|
|
256
311
|
if (!value.ok)
|
|
257
|
-
return {
|
|
312
|
+
return {
|
|
313
|
+
ok: false,
|
|
314
|
+
code: "not_allowed",
|
|
315
|
+
message: value.message,
|
|
316
|
+
...(value.unmet ? { unmet: value.unmet } : {}),
|
|
317
|
+
};
|
|
258
318
|
this.emit(sessionId, outcome.goal);
|
|
259
319
|
return {
|
|
260
320
|
ok: true,
|
|
@@ -263,6 +323,33 @@ export class GoalService {
|
|
|
263
323
|
...(value.needed ? { needed: value.needed } : {}),
|
|
264
324
|
};
|
|
265
325
|
}
|
|
326
|
+
/**
|
|
327
|
+
* The USER completes the goal over unmet required criteria (O6). A separate method on purpose:
|
|
328
|
+
* `report` is the model's only door and has no way to carry an override, so the model cannot
|
|
329
|
+
* reach this path whatever it sends. Hosts call it from a user action only (Phase 5 surfaces).
|
|
330
|
+
*/
|
|
331
|
+
overrideComplete(sessionId, input, expect) {
|
|
332
|
+
const reason = input.reason.trim();
|
|
333
|
+
if (!reason)
|
|
334
|
+
return { ok: false, code: "invalid", message: "An override needs a reason." };
|
|
335
|
+
const outcome = this.store.update(sessionId, (goal) => {
|
|
336
|
+
const ledger = this.options.proof?.ledger(sessionId);
|
|
337
|
+
const result = userOverrideComplete(goal, this.clock(), {
|
|
338
|
+
reason,
|
|
339
|
+
proof: {
|
|
340
|
+
criteria: ledger ? criteriaForGoal(ledger, goal.goalId) : [],
|
|
341
|
+
linkedEvidence: goal.proof?.linkedEvidence ?? [],
|
|
342
|
+
},
|
|
343
|
+
});
|
|
344
|
+
return result.ok ? { goal: result.goal, value: result } : { value: result };
|
|
345
|
+
}, expect);
|
|
346
|
+
if (!outcome.ok)
|
|
347
|
+
return { ok: false, code: outcome.code, message: outcome.message };
|
|
348
|
+
const result = outcome.value;
|
|
349
|
+
if (!result.ok)
|
|
350
|
+
return { ok: false, code: "not_allowed", message: result.message };
|
|
351
|
+
return this.done(sessionId, { ok: true, goal: result.goal });
|
|
352
|
+
}
|
|
266
353
|
/** Startup: goals whose driver process is gone pause with reason `restart`. */
|
|
267
354
|
recover() {
|
|
268
355
|
const paused = this.store.pauseOrphans(this.clock());
|
package/dist/goal/store.js
CHANGED
|
@@ -13,6 +13,24 @@ function parseEvidence(raw) {
|
|
|
13
13
|
return undefined;
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
|
+
function parseProof(raw) {
|
|
17
|
+
if (typeof raw !== "string" || !raw)
|
|
18
|
+
return undefined;
|
|
19
|
+
try {
|
|
20
|
+
const value = JSON.parse(raw);
|
|
21
|
+
return value && Array.isArray(value.criteria) && Array.isArray(value.unmet)
|
|
22
|
+
? {
|
|
23
|
+
criteria: value.criteria,
|
|
24
|
+
unmet: value.unmet,
|
|
25
|
+
linkedEvidence: Array.isArray(value.linkedEvidence) ? value.linkedEvidence : [],
|
|
26
|
+
...(value.override ? { override: value.override } : {}),
|
|
27
|
+
}
|
|
28
|
+
: undefined;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
16
34
|
export function goalFromRow(row) {
|
|
17
35
|
const status = String(row.status);
|
|
18
36
|
return {
|
|
@@ -26,6 +44,7 @@ export function goalFromRow(row) {
|
|
|
26
44
|
...(parseEvidence(row.evidence)
|
|
27
45
|
? { evidence: parseEvidence(row.evidence) }
|
|
28
46
|
: {}),
|
|
47
|
+
...(parseProof(row.proof_json) ? { proof: parseProof(row.proof_json) } : {}),
|
|
29
48
|
epoch: Number(row.epoch ?? 1),
|
|
30
49
|
...(num(row.token_budget) !== undefined
|
|
31
50
|
? { tokenBudget: num(row.token_budget) }
|
|
@@ -55,7 +74,7 @@ export function goalFromRow(row) {
|
|
|
55
74
|
const COLUMNS = `session, goal_id, objective, status, reason, detail, summary, evidence, epoch,
|
|
56
75
|
token_budget, tokens_used, turns_used, max_turns, max_wall_ms, active_ms, no_tool_streak,
|
|
57
76
|
repeat_streak, last_reply_hash, blocked_streak, blocked_run, continuations, inflight,
|
|
58
|
-
last_run_id, kickoff_sent, owner_pid, created_at, updated_at, completed_at`;
|
|
77
|
+
last_run_id, kickoff_sent, owner_pid, created_at, updated_at, completed_at, proof_json`;
|
|
59
78
|
export class GoalStore {
|
|
60
79
|
db;
|
|
61
80
|
constructor(db) {
|
|
@@ -89,8 +108,8 @@ export class GoalStore {
|
|
|
89
108
|
continuations=excluded.continuations, inflight=excluded.inflight,
|
|
90
109
|
last_run_id=excluded.last_run_id, kickoff_sent=excluded.kickoff_sent,
|
|
91
110
|
owner_pid=excluded.owner_pid, updated_at=excluded.updated_at,
|
|
92
|
-
completed_at=excluded.completed_at`)
|
|
93
|
-
.run(goal.sessionId, goal.goalId, goal.objective, goal.status, goal.reason ?? null, goal.detail ?? null, goal.summary ?? null, goal.evidence?.length ? JSON.stringify(goal.evidence) : null, goal.epoch, goal.tokenBudget ?? null, goal.tokensUsed, goal.turnsUsed, goal.maxTurns, goal.maxWallMs, goal.activeMs, goal.noToolStreak, goal.repeatStreak, goal.lastReplyHash ?? null, goal.blockedStreak, goal.blockedRun ?? null, goal.continuations, goal.inflight ?? null, goal.lastRunId ?? null, goal.kickoffSent ? 1 : 0, goal.ownerPid ?? null, goal.createdAt, goal.updatedAt, goal.completedAt ?? null);
|
|
111
|
+
completed_at=excluded.completed_at, proof_json=excluded.proof_json`)
|
|
112
|
+
.run(goal.sessionId, goal.goalId, goal.objective, goal.status, goal.reason ?? null, goal.detail ?? null, goal.summary ?? null, goal.evidence?.length ? JSON.stringify(goal.evidence) : null, goal.epoch, goal.tokenBudget ?? null, goal.tokensUsed, goal.turnsUsed, goal.maxTurns, goal.maxWallMs, goal.activeMs, goal.noToolStreak, goal.repeatStreak, goal.lastReplyHash ?? null, goal.blockedStreak, goal.blockedRun ?? null, goal.continuations, goal.inflight ?? null, goal.lastRunId ?? null, goal.kickoffSent ? 1 : 0, goal.ownerPid ?? null, goal.createdAt, goal.updatedAt, goal.completedAt ?? null, goal.proof ? JSON.stringify(goal.proof) : null);
|
|
94
113
|
}
|
|
95
114
|
/**
|
|
96
115
|
* Replaces (or creates) the goal of a session. `build` gets the previous goal, if any, so the
|
package/dist/index.d.ts
CHANGED
|
@@ -41,6 +41,21 @@ export { ToolRegistry } from "./core/registry.ts";
|
|
|
41
41
|
export { RunClock } from "./core/run-clock.ts";
|
|
42
42
|
export { AgentRunner, type CompactionResult, defaultTokenBudget, type RunnerOptions, type RunnerSettingsPatch, type RunOptions, } from "./core/runner.ts";
|
|
43
43
|
export { CircuitBreaker, type CircuitBreakerOptions, type CircuitState, DECISION_LIMITS, DEFAULT_DECISIONS_CONFIG, type DecisionAttempt, DecisionError, type DecisionMetricEntry, DecisionMetrics, type DecisionMetricsSnapshot, type DecisionOutcome, DecisionRegistry, DecisionService, type DecisionServiceDeps, type DecisionStatus, type DecisionsConfig, formatDecisionStatsSection, formatDecisionsReport, HEALTH_TIMEOUT_MS, type RegisteredDecisionProvider, splitSupported, validateAnswers, validateRequest, } from "./decisions/index.ts";
|
|
44
|
+
export { canonicalJson, hashCanonical } from "./eval/canonical.ts";
|
|
45
|
+
export { CASSETTE_SUFFIX, type Cassette, type CassetteCall, CassetteFormatError, type CassetteHeader, type CassetteMeta, CassetteRecorder, CassetteWriteError, MAX_CASSETTE_BYTES, MAX_CASSETTE_DIR_BYTES, parseCassette, RecordingProvider, ReplayDivergenceError, ReplayExhausted, ReplayProvider, ReplayUnconsumedError, readCassette, serializeCassette, type WriteOptions, writeCassette, } from "./eval/cassette.ts";
|
|
46
|
+
export { type Conditions, compareReports, compareVariants, EvalCompareError, type EvalComparison, type Interval, type MetricDelta, parseReport, renderComparisonMarkdown, renderVariantComparisonMarkdown, type TaskComparison, type TokenComparison, type VariantComparison, type Verdict, } from "./eval/compare.ts";
|
|
47
|
+
export { CONFIG_HASH_COVERAGE, type ConfigHashExtras, checkTaskPolicy, computeConfigHash, type EvalApplication, EvalBudgetExceeded, type EvalRunMode, EvalRunRefusedError, type InvokerPermissions, type RunTaskOptions, runTask, } from "./eval/engine.ts";
|
|
48
|
+
export { buildExportBundle, type EvalExportBundle, EvalExportRefusedError, type ExportContext, } from "./eval/export.ts";
|
|
49
|
+
export { computeFingerprint, type FingerprintDiff, type FingerprintDiffEntry, } from "./eval/fingerprint.ts";
|
|
50
|
+
export { EvalPathError, resolveInside } from "./eval/paths.ts";
|
|
51
|
+
export { interleaveVariants, mulberry32, type PlannedRun, planRuns, type VariantStep, } from "./eval/plan.ts";
|
|
52
|
+
export { type BuildReportOptions, buildReport, EvalReportError, estimateRunCost, lowPowerTasks, MIN_POWER_K, renderReportMarkdown, } from "./eval/report.ts";
|
|
53
|
+
export { type RunEvaluationOptions, type RunEvaluationResult, type RunVariantsOptions, runEvaluation, runVariantEvaluations, } from "./eval/run-suite.ts";
|
|
54
|
+
export { findSecrets, redactSecrets } from "./eval/secrets.ts";
|
|
55
|
+
export { median, passAtK, passPowK, sampleVariance, wilsonInterval, } from "./eval/stats.ts";
|
|
56
|
+
export { EvalSuiteError, type LoadedSuite, loadSuite, MAX_VERIFIER_TIMEOUT_MS, } from "./eval/suite.ts";
|
|
57
|
+
export { applyConfigOverrides, EvalVariantError, loadVariantsFile, resolveVariants, type VariantsFile, } from "./eval/variants.ts";
|
|
58
|
+
export { runVerifier, runVerifiers, snapshotTree, type ToolEvent, type TreeSnapshot, type VerifierContext, type VerifierResult, type VerifierStatus, } from "./eval/verifiers.ts";
|
|
44
59
|
export { type ExtensionConflict, ExtensionRegistry } from "./extensions/registry.ts";
|
|
45
60
|
export { ForgeError } from "./forge/errors.ts";
|
|
46
61
|
export { downloadSize as pyodideDownloadSize, inspectPyodide, installPyodide, PyodideIntegrityError, pyodideDir, } from "./forge/python/pyodide-install.ts";
|
|
@@ -55,9 +70,10 @@ export { McpConnector, type McpServerInfo, type McpStatus, type McpToolInfo, } f
|
|
|
55
70
|
export { describePermissionStatus, type EffectState, effectState, FULL_ACCESS_WARNING, type ModeFlags, type ModePolicy, modeFromFlags, modeToPreset, PERMISSION_MODE_TABLE, PERMISSION_MODES, PERMISSION_MODES_LOCKED, PERMISSION_USAGE, type PermissionCommand, type PermissionModeLabel, type PermissionModeSpec, parsePermissionCommand, parsePermissionMode, presetToMode, READ_ONLY_POLICY, } from "./permissions/modes.ts";
|
|
56
71
|
export { DIAGRAM_MAX_BYTES, DIAGRAM_MAX_NODES, DIAGRAM_SYNTAXES, DIAGRAMS_MAX_ALLOWED, mermaidProblem, type PlanDiagram, validateDiagrams, } from "./plan/diagrams.ts";
|
|
57
72
|
export { buildPlanManifest, parsePlanHeadings, slugifyHeading, } from "./plan/manifest.ts";
|
|
58
|
-
export { claimApprovedPlan, currentPlan, discardApprovedPlan, IMPLEMENTATION_AGENT_ID, implementationPrompt, PLAN_CONTEXT_MAX_CHARS, PLAN_OPTIONS, PLAN_QUESTION_ID, PLAN_REVIEW_TITLE, PLAN_TEXT_KEY, type PlanFollowUp, type PlanState, type PlanStatus, planHash, readPlanState, settlePlanRun, withdrawPendingPlan, } from "./plan/state.ts";
|
|
73
|
+
export { claimApprovedPlan, currentPlan, discardApprovedPlan, IMPLEMENTATION_AGENT_ID, implementationPrompt, PLAN_CONTEXT_MAX_CHARS, PLAN_CRITERIA_KEY, PLAN_OPTIONS, PLAN_QUESTION_ID, PLAN_REVIEW_TITLE, PLAN_TEXT_KEY, type PlanFollowUp, type PlanState, type PlanStatus, planHash, readPlanState, settlePlanRun, withdrawPendingPlan, } from "./plan/state.ts";
|
|
59
74
|
export { discoverPlugins, type PluginEnvironment, PluginHost, type PluginHostOptions, pluginPrefix, type ViewInfo, ViewRunError, } from "./plugins/host.ts";
|
|
60
75
|
export { type CliInstallOptions, cliInstall, cliUpdateAll, INSTALL_TIMEOUT_MS, type InstallPluginInput, type InstallPluginResult, type InstallRunner, installedGlobalPluginPackages, installedNpmPlugins, installPlugin, isPeerConflict, npmInstallTarget, type PluginSpec, parsePluginSpec, pluginTrustRemark, printInstallResult, registerPluginInstallTool, sanitizeNpmError, updateAllPlugins, } from "./plugins/install.ts";
|
|
76
|
+
export * from "./proof/index.ts";
|
|
61
77
|
export { ActiveProvider, ProviderRegistry, UnconfiguredProvider } from "./providers/registry.ts";
|
|
62
78
|
export { availableProviderModels, type ProviderModelCatalog, resolveProviderModel, } from "./providers/routing.ts";
|
|
63
79
|
export { maskSecret, type ProviderProfile, type ProviderSettings, ProviderSettingsStore, } from "./providers/settings.ts";
|
|
@@ -69,7 +85,7 @@ export { type ExternalDirectoryDecision, type ExternalDirectoryHandler, type Ext
|
|
|
69
85
|
export { BlobStore, type BlobStoreOptions, BlobTooLarge } from "./runtime/blobs.ts";
|
|
70
86
|
export { clipLines, unifiedPatch } from "./runtime/diff.ts";
|
|
71
87
|
export { exists, fileSize, readHead, readJson, readText, which } from "./runtime/fs.ts";
|
|
72
|
-
export { newId, ulid } from "./runtime/ids.ts";
|
|
88
|
+
export { type IdSource, newId, newUuid, seededIdSource, setIdSource, ulid, } from "./runtime/ids.ts";
|
|
73
89
|
export { defaultGlobalRoots, isPathSpec, PLUGIN_KEYWORD, resolvePluginSpec, } from "./runtime/modules.ts";
|
|
74
90
|
export { findWorkspace, inside, outsideRootsMessage, safePath, workspaceKey, } from "./runtime/paths.ts";
|
|
75
91
|
export { isMissingCommand, type ProcessResult, RIPGREP_INSTALL_HINT, runProcess, } from "./runtime/process.ts";
|
package/dist/index.js
CHANGED
|
@@ -40,6 +40,21 @@ export { ToolRegistry } from "./core/registry.js";
|
|
|
40
40
|
export { RunClock } from "./core/run-clock.js";
|
|
41
41
|
export { AgentRunner, defaultTokenBudget, } from "./core/runner.js";
|
|
42
42
|
export { CircuitBreaker, DECISION_LIMITS, DEFAULT_DECISIONS_CONFIG, DecisionError, DecisionMetrics, DecisionRegistry, DecisionService, formatDecisionStatsSection, formatDecisionsReport, HEALTH_TIMEOUT_MS, splitSupported, validateAnswers, validateRequest, } from "./decisions/index.js";
|
|
43
|
+
export { canonicalJson, hashCanonical } from "./eval/canonical.js";
|
|
44
|
+
export { CASSETTE_SUFFIX, CassetteFormatError, CassetteRecorder, CassetteWriteError, MAX_CASSETTE_BYTES, MAX_CASSETTE_DIR_BYTES, parseCassette, RecordingProvider, ReplayDivergenceError, ReplayExhausted, ReplayProvider, ReplayUnconsumedError, readCassette, serializeCassette, writeCassette, } from "./eval/cassette.js";
|
|
45
|
+
export { compareReports, compareVariants, EvalCompareError, parseReport, renderComparisonMarkdown, renderVariantComparisonMarkdown, } from "./eval/compare.js";
|
|
46
|
+
export { CONFIG_HASH_COVERAGE, checkTaskPolicy, computeConfigHash, EvalBudgetExceeded, EvalRunRefusedError, runTask, } from "./eval/engine.js";
|
|
47
|
+
export { buildExportBundle, EvalExportRefusedError, } from "./eval/export.js";
|
|
48
|
+
export { computeFingerprint, } from "./eval/fingerprint.js";
|
|
49
|
+
export { EvalPathError, resolveInside } from "./eval/paths.js";
|
|
50
|
+
export { interleaveVariants, mulberry32, planRuns, } from "./eval/plan.js";
|
|
51
|
+
export { buildReport, EvalReportError, estimateRunCost, lowPowerTasks, MIN_POWER_K, renderReportMarkdown, } from "./eval/report.js";
|
|
52
|
+
export { runEvaluation, runVariantEvaluations, } from "./eval/run-suite.js";
|
|
53
|
+
export { findSecrets, redactSecrets } from "./eval/secrets.js";
|
|
54
|
+
export { median, passAtK, passPowK, sampleVariance, wilsonInterval, } from "./eval/stats.js";
|
|
55
|
+
export { EvalSuiteError, loadSuite, MAX_VERIFIER_TIMEOUT_MS, } from "./eval/suite.js";
|
|
56
|
+
export { applyConfigOverrides, EvalVariantError, loadVariantsFile, resolveVariants, } from "./eval/variants.js";
|
|
57
|
+
export { runVerifier, runVerifiers, snapshotTree, } from "./eval/verifiers.js";
|
|
43
58
|
export { ExtensionRegistry } from "./extensions/registry.js";
|
|
44
59
|
export { ForgeError } from "./forge/errors.js";
|
|
45
60
|
export { downloadSize as pyodideDownloadSize, inspectPyodide, installPyodide, PyodideIntegrityError, pyodideDir, } from "./forge/python/pyodide-install.js";
|
|
@@ -54,9 +69,10 @@ export { McpConnector, } from "./mcp/connector.js";
|
|
|
54
69
|
export { describePermissionStatus, effectState, FULL_ACCESS_WARNING, modeFromFlags, modeToPreset, PERMISSION_MODE_TABLE, PERMISSION_MODES, PERMISSION_MODES_LOCKED, PERMISSION_USAGE, parsePermissionCommand, parsePermissionMode, presetToMode, READ_ONLY_POLICY, } from "./permissions/modes.js";
|
|
55
70
|
export { DIAGRAM_MAX_BYTES, DIAGRAM_MAX_NODES, DIAGRAM_SYNTAXES, DIAGRAMS_MAX_ALLOWED, mermaidProblem, validateDiagrams, } from "./plan/diagrams.js";
|
|
56
71
|
export { buildPlanManifest, parsePlanHeadings, slugifyHeading, } from "./plan/manifest.js";
|
|
57
|
-
export { claimApprovedPlan, currentPlan, discardApprovedPlan, IMPLEMENTATION_AGENT_ID, implementationPrompt, PLAN_CONTEXT_MAX_CHARS, PLAN_OPTIONS, PLAN_QUESTION_ID, PLAN_REVIEW_TITLE, PLAN_TEXT_KEY, planHash, readPlanState, settlePlanRun, withdrawPendingPlan, } from "./plan/state.js";
|
|
72
|
+
export { claimApprovedPlan, currentPlan, discardApprovedPlan, IMPLEMENTATION_AGENT_ID, implementationPrompt, PLAN_CONTEXT_MAX_CHARS, PLAN_CRITERIA_KEY, PLAN_OPTIONS, PLAN_QUESTION_ID, PLAN_REVIEW_TITLE, PLAN_TEXT_KEY, planHash, readPlanState, settlePlanRun, withdrawPendingPlan, } from "./plan/state.js";
|
|
58
73
|
export { discoverPlugins, PluginHost, pluginPrefix, ViewRunError, } from "./plugins/host.js";
|
|
59
74
|
export { cliInstall, cliUpdateAll, INSTALL_TIMEOUT_MS, installedGlobalPluginPackages, installedNpmPlugins, installPlugin, isPeerConflict, npmInstallTarget, parsePluginSpec, pluginTrustRemark, printInstallResult, registerPluginInstallTool, sanitizeNpmError, updateAllPlugins, } from "./plugins/install.js";
|
|
75
|
+
export * from "./proof/index.js";
|
|
60
76
|
export { ActiveProvider, ProviderRegistry, UnconfiguredProvider } from "./providers/registry.js";
|
|
61
77
|
export { availableProviderModels, resolveProviderModel, } from "./providers/routing.js";
|
|
62
78
|
export { maskSecret, ProviderSettingsStore, } from "./providers/settings.js";
|
|
@@ -68,7 +84,7 @@ export { PathAccess, } from "./runtime/access.js";
|
|
|
68
84
|
export { BlobStore, BlobTooLarge } from "./runtime/blobs.js";
|
|
69
85
|
export { clipLines, unifiedPatch } from "./runtime/diff.js";
|
|
70
86
|
export { exists, fileSize, readHead, readJson, readText, which } from "./runtime/fs.js";
|
|
71
|
-
export { newId, ulid } from "./runtime/ids.js";
|
|
87
|
+
export { newId, newUuid, seededIdSource, setIdSource, ulid, } from "./runtime/ids.js";
|
|
72
88
|
export { defaultGlobalRoots, isPathSpec, PLUGIN_KEYWORD, resolvePluginSpec, } from "./runtime/modules.js";
|
|
73
89
|
export { findWorkspace, inside, outsideRootsMessage, safePath, workspaceKey, } from "./runtime/paths.js";
|
|
74
90
|
export { isMissingCommand, RIPGREP_INSTALL_HINT, runProcess, } from "./runtime/process.js";
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation of the acceptance criteria a model proposes in `exit_plan` (specs/011 3.6, O3).
|
|
3
|
+
* Pure: no I/O. The result is a DRAFT: nothing here creates a criterion; the plan decision of
|
|
4
|
+
* the user does (`exit-plan.ts`). The model may only propose verifier kinds Alisio can run today
|
|
5
|
+
* (`command`, `file`, `data-check`, `coverage`, `plugin`, `manual`) and plain workspace-relative scopes.
|
|
6
|
+
*/
|
|
7
|
+
import type { PlanCriterionDraft } from "@alisio/sdk";
|
|
8
|
+
export declare const CRITERIA_MAX = 20;
|
|
9
|
+
export declare const CRITERION_TEXT_MAX = 500;
|
|
10
|
+
export interface DroppedCriterion {
|
|
11
|
+
/** Zero-based position in the proposed list. */
|
|
12
|
+
index: number;
|
|
13
|
+
reason: string;
|
|
14
|
+
}
|
|
15
|
+
export interface ValidatedCriteria {
|
|
16
|
+
accepted: PlanCriterionDraft[];
|
|
17
|
+
dropped: DroppedCriterion[];
|
|
18
|
+
}
|
|
19
|
+
/** Turns the model's `acceptanceCriteria` input into drafts, reporting what it could not keep. */
|
|
20
|
+
export declare function validateCriteriaDrafts(raw: unknown): ValidatedCriteria;
|
|
21
|
+
/**
|
|
22
|
+
* Applies the user's per-draft decisions (`plan:criteria`) to the drafts of the plan they approved.
|
|
23
|
+
* `undefined` selection keeps every draft (the plain "approve"). Anything malformed, any index
|
|
24
|
+
* outside the list or an empty edit returns `undefined` for "nothing is activated": a selection
|
|
25
|
+
* the user did not make cleanly never activates a criterion (fail closed).
|
|
26
|
+
*/
|
|
27
|
+
export declare function applyCriteriaSelection(drafts: readonly PlanCriterionDraft[], raw: unknown): {
|
|
28
|
+
kept: PlanCriterionDraft[];
|
|
29
|
+
rejected: number;
|
|
30
|
+
} | undefined;
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
export const CRITERIA_MAX = 20;
|
|
2
|
+
export const CRITERION_TEXT_MAX = 500;
|
|
3
|
+
const COMMAND_MAX = 1000;
|
|
4
|
+
const PATH_MAX = 500;
|
|
5
|
+
const PATTERN_MAX = 200;
|
|
6
|
+
const SCOPE_ENTRIES_MAX = 20;
|
|
7
|
+
const SCOPE_ENTRY_MAX = 256;
|
|
8
|
+
const EXIT_CODES_MAX = 10;
|
|
9
|
+
const DATASET_ID_MAX = 64;
|
|
10
|
+
const IDENTIFIER_MAX = 200;
|
|
11
|
+
const EXPECTED_MAX = 500;
|
|
12
|
+
const PLUGIN_INPUT_MAX = 4096;
|
|
13
|
+
const PLUGIN_VERIFIER_ID = /^[a-z0-9][a-z0-9._-]{0,63}:[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
14
|
+
const DATA_CHECKS = new Set(["row_count", "sum", "equals"]);
|
|
15
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16
|
+
const text = (value, max) => typeof value === "string" && value.trim() && value.length <= max ? value : undefined;
|
|
17
|
+
function verifierOf(raw) {
|
|
18
|
+
if (!isRecord(raw))
|
|
19
|
+
return "verifier must be an object";
|
|
20
|
+
if (raw.kind === "manual")
|
|
21
|
+
return { kind: "manual" };
|
|
22
|
+
if (raw.kind === "command") {
|
|
23
|
+
const command = text(raw.command, COMMAND_MAX)?.trim();
|
|
24
|
+
if (!command)
|
|
25
|
+
return "a command verifier needs a command";
|
|
26
|
+
const cwd = raw.cwd === undefined ? undefined : text(raw.cwd, PATH_MAX);
|
|
27
|
+
if (raw.cwd !== undefined && !cwd)
|
|
28
|
+
return "cwd must be a short string";
|
|
29
|
+
if (cwd && (isAbsoluteLike(cwd) || hasParentSegment(cwd)))
|
|
30
|
+
return "cwd must stay in the workspace";
|
|
31
|
+
const codes = raw.successExitCodes;
|
|
32
|
+
if (codes !== undefined &&
|
|
33
|
+
!(Array.isArray(codes) &&
|
|
34
|
+
codes.length > 0 &&
|
|
35
|
+
codes.length <= EXIT_CODES_MAX &&
|
|
36
|
+
codes.every((c) => Number.isInteger(c) && c >= 0 && c <= 255)))
|
|
37
|
+
return "successExitCodes must be a short list of exit codes";
|
|
38
|
+
const pattern = raw.outputPattern === undefined ? undefined : text(raw.outputPattern, PATTERN_MAX);
|
|
39
|
+
if (raw.outputPattern !== undefined && !pattern)
|
|
40
|
+
return "outputPattern must be a short string";
|
|
41
|
+
return {
|
|
42
|
+
kind: "command",
|
|
43
|
+
command,
|
|
44
|
+
...(cwd ? { cwd } : {}),
|
|
45
|
+
...(codes ? { successExitCodes: codes } : {}),
|
|
46
|
+
...(pattern ? { outputPattern: pattern } : {}),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (raw.kind === "file") {
|
|
50
|
+
const path = text(raw.path, PATH_MAX)?.trim();
|
|
51
|
+
if (!path || isAbsoluteLike(path) || hasParentSegment(path))
|
|
52
|
+
return "a file verifier needs a workspace-relative path";
|
|
53
|
+
const sha = typeof raw.sha256 === "string" && /^[0-9a-f]{64}$/.test(raw.sha256);
|
|
54
|
+
const pattern = raw.pattern === undefined ? undefined : text(raw.pattern, PATTERN_MAX);
|
|
55
|
+
if (raw.pattern !== undefined && !pattern)
|
|
56
|
+
return "pattern must be a short string";
|
|
57
|
+
if (raw.sha256 !== undefined && !sha)
|
|
58
|
+
return "sha256 must be 64 hex characters";
|
|
59
|
+
return {
|
|
60
|
+
kind: "file",
|
|
61
|
+
path,
|
|
62
|
+
...(sha ? { sha256: raw.sha256 } : {}),
|
|
63
|
+
...(pattern ? { pattern } : {}),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (raw.kind === "data-check")
|
|
67
|
+
return dataCheckOf(raw);
|
|
68
|
+
if (raw.kind === "coverage") {
|
|
69
|
+
const command = text(raw.command, COMMAND_MAX)?.trim();
|
|
70
|
+
if (!command)
|
|
71
|
+
return "a coverage verifier needs a command";
|
|
72
|
+
const min = raw.minPercent;
|
|
73
|
+
if (typeof min !== "number" || !Number.isFinite(min) || min < 0 || min > 100)
|
|
74
|
+
return "a coverage verifier needs minPercent between 0 and 100";
|
|
75
|
+
return { kind: "coverage", command, minPercent: min };
|
|
76
|
+
}
|
|
77
|
+
if (raw.kind === "plugin") {
|
|
78
|
+
const verifierId = typeof raw.verifierId === "string" ? raw.verifierId : "";
|
|
79
|
+
if (!PLUGIN_VERIFIER_ID.test(verifierId))
|
|
80
|
+
return "a plugin verifier needs a verifierId like pluginId:verifier";
|
|
81
|
+
if (raw.input === undefined)
|
|
82
|
+
return { kind: "plugin", verifierId };
|
|
83
|
+
let size = Number.POSITIVE_INFINITY;
|
|
84
|
+
try {
|
|
85
|
+
size = JSON.stringify(raw.input)?.length ?? Number.POSITIVE_INFINITY;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
/* Not serializable: refused below. */
|
|
89
|
+
}
|
|
90
|
+
if (size > PLUGIN_INPUT_MAX)
|
|
91
|
+
return "a plugin verifier input must be short JSON";
|
|
92
|
+
return { kind: "plugin", verifierId, input: raw.input };
|
|
93
|
+
}
|
|
94
|
+
return "that verifier kind cannot be proposed (use command, file, data-check, coverage, plugin or manual)";
|
|
95
|
+
}
|
|
96
|
+
function dataCheckOf(raw) {
|
|
97
|
+
const datasetId = text(raw.datasetId, DATASET_ID_MAX)?.trim();
|
|
98
|
+
if (!datasetId)
|
|
99
|
+
return "a data-check verifier needs a datasetId";
|
|
100
|
+
const check = typeof raw.check === "string" ? raw.check : "";
|
|
101
|
+
if (!DATA_CHECKS.has(check))
|
|
102
|
+
return "check must be row_count, sum or equals";
|
|
103
|
+
const expected = raw.expected;
|
|
104
|
+
const expectedOk = (typeof expected === "number" && Number.isFinite(expected)) ||
|
|
105
|
+
(typeof expected === "string" && expected.length > 0 && expected.length <= EXPECTED_MAX);
|
|
106
|
+
if (!expectedOk)
|
|
107
|
+
return "a data-check verifier needs an expected number or short string";
|
|
108
|
+
const column = raw.column === undefined ? undefined : text(raw.column, IDENTIFIER_MAX);
|
|
109
|
+
if (raw.column !== undefined && !column)
|
|
110
|
+
return "column must be a short string";
|
|
111
|
+
if (check !== "row_count" && !column)
|
|
112
|
+
return `a ${check} check needs a column`;
|
|
113
|
+
const table = raw.table === undefined ? undefined : text(raw.table, IDENTIFIER_MAX);
|
|
114
|
+
if (raw.table !== undefined && !table)
|
|
115
|
+
return "table must be a short string";
|
|
116
|
+
return {
|
|
117
|
+
kind: "data-check",
|
|
118
|
+
datasetId,
|
|
119
|
+
check: check,
|
|
120
|
+
...(column ? { column } : {}),
|
|
121
|
+
expected: expected,
|
|
122
|
+
...(table ? { table } : {}),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
const isAbsoluteLike = (path) => path.startsWith("/") || path.startsWith("\\") || /^[A-Za-z]:/.test(path);
|
|
126
|
+
const hasParentSegment = (path) => path.split(/[\\/]/).includes("..");
|
|
127
|
+
function scopeOf(raw) {
|
|
128
|
+
if (!Array.isArray(raw) || raw.length === 0 || raw.length > SCOPE_ENTRIES_MAX)
|
|
129
|
+
return "scope must be a short list of globs";
|
|
130
|
+
const out = [];
|
|
131
|
+
for (const entry of raw) {
|
|
132
|
+
const glob = text(entry, SCOPE_ENTRY_MAX)?.trim();
|
|
133
|
+
if (!glob)
|
|
134
|
+
return "every scope entry must be a short string";
|
|
135
|
+
if (isAbsoluteLike(glob) || hasParentSegment(glob))
|
|
136
|
+
return "scope entries must stay inside the workspace";
|
|
137
|
+
out.push(glob);
|
|
138
|
+
}
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
141
|
+
/** Turns the model's `acceptanceCriteria` input into drafts, reporting what it could not keep. */
|
|
142
|
+
export function validateCriteriaDrafts(raw) {
|
|
143
|
+
const accepted = [];
|
|
144
|
+
const dropped = [];
|
|
145
|
+
if (!Array.isArray(raw)) {
|
|
146
|
+
return { accepted, dropped: [{ index: 0, reason: "acceptanceCriteria must be an array" }] };
|
|
147
|
+
}
|
|
148
|
+
raw.forEach((entry, index) => {
|
|
149
|
+
if (accepted.length >= CRITERIA_MAX) {
|
|
150
|
+
dropped.push({ index, reason: `more than ${CRITERIA_MAX} criteria were proposed` });
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (!isRecord(entry)) {
|
|
154
|
+
dropped.push({ index, reason: "a criterion must be an object" });
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const statement = typeof entry.text === "string" ? entry.text.replace(/\s+/g, " ").trim() : "";
|
|
158
|
+
if (!statement) {
|
|
159
|
+
dropped.push({ index, reason: "a criterion needs text" });
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
let verifier;
|
|
163
|
+
if (entry.verifier !== undefined) {
|
|
164
|
+
const checked = verifierOf(entry.verifier);
|
|
165
|
+
if (typeof checked === "string") {
|
|
166
|
+
dropped.push({ index, reason: checked });
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
verifier = checked;
|
|
170
|
+
}
|
|
171
|
+
let scope;
|
|
172
|
+
if (entry.scope !== undefined) {
|
|
173
|
+
const checked = scopeOf(entry.scope);
|
|
174
|
+
if (typeof checked === "string") {
|
|
175
|
+
dropped.push({ index, reason: checked });
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
scope = checked;
|
|
179
|
+
}
|
|
180
|
+
accepted.push({
|
|
181
|
+
text: statement.length > CRITERION_TEXT_MAX ? statement.slice(0, CRITERION_TEXT_MAX) : statement,
|
|
182
|
+
required: entry.required === true,
|
|
183
|
+
...(verifier ? { verifier } : {}),
|
|
184
|
+
...(scope ? { scope } : {}),
|
|
185
|
+
origin: "inferred",
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
return { accepted, dropped };
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Applies the user's per-draft decisions (`plan:criteria`) to the drafts of the plan they approved.
|
|
192
|
+
* `undefined` selection keeps every draft (the plain "approve"). Anything malformed, any index
|
|
193
|
+
* outside the list or an empty edit returns `undefined` for "nothing is activated": a selection
|
|
194
|
+
* the user did not make cleanly never activates a criterion (fail closed).
|
|
195
|
+
*/
|
|
196
|
+
export function applyCriteriaSelection(drafts, raw) {
|
|
197
|
+
if (raw === undefined || raw === "")
|
|
198
|
+
return { kept: [...drafts], rejected: 0 };
|
|
199
|
+
let selection;
|
|
200
|
+
try {
|
|
201
|
+
selection = typeof raw === "string" ? JSON.parse(raw) : undefined;
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
if (!isRecord(selection))
|
|
207
|
+
return undefined;
|
|
208
|
+
const index = (key) => {
|
|
209
|
+
const n = typeof key === "string" ? Number(key) : key;
|
|
210
|
+
return Number.isInteger(n) && n >= 0 && n < drafts.length ? n : undefined;
|
|
211
|
+
};
|
|
212
|
+
const rejected = new Set();
|
|
213
|
+
if (selection.reject !== undefined) {
|
|
214
|
+
if (!Array.isArray(selection.reject))
|
|
215
|
+
return undefined;
|
|
216
|
+
for (const entry of selection.reject) {
|
|
217
|
+
const i = index(entry);
|
|
218
|
+
if (i === undefined)
|
|
219
|
+
return undefined;
|
|
220
|
+
rejected.add(i);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const edits = new Map();
|
|
224
|
+
if (selection.edit !== undefined) {
|
|
225
|
+
if (!isRecord(selection.edit))
|
|
226
|
+
return undefined;
|
|
227
|
+
for (const [key, value] of Object.entries(selection.edit)) {
|
|
228
|
+
const i = index(key);
|
|
229
|
+
const statement = typeof value === "string" ? value.replace(/\s+/g, " ").trim() : "";
|
|
230
|
+
if (i === undefined || !statement)
|
|
231
|
+
return undefined;
|
|
232
|
+
edits.set(i, statement.slice(0, CRITERION_TEXT_MAX));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
const required = new Map();
|
|
236
|
+
if (selection.required !== undefined) {
|
|
237
|
+
if (!isRecord(selection.required))
|
|
238
|
+
return undefined;
|
|
239
|
+
for (const [key, value] of Object.entries(selection.required)) {
|
|
240
|
+
const i = index(key);
|
|
241
|
+
if (i === undefined || typeof value !== "boolean")
|
|
242
|
+
return undefined;
|
|
243
|
+
required.set(i, value);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
const kept = drafts.flatMap((draft, i) => rejected.has(i)
|
|
247
|
+
? []
|
|
248
|
+
: [
|
|
249
|
+
{
|
|
250
|
+
...draft,
|
|
251
|
+
text: edits.get(i) ?? draft.text,
|
|
252
|
+
required: required.get(i) ?? draft.required,
|
|
253
|
+
},
|
|
254
|
+
]);
|
|
255
|
+
return { kept, rejected: rejected.size };
|
|
256
|
+
}
|
package/dist/plan/exit-plan.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AskQuestionsRequest, AskQuestionsResult } from "@alisio/sdk";
|
|
2
2
|
import type { ToolRegistry } from "../core/registry.ts";
|
|
3
|
+
import type { NewCriterion } from "../proof/types.ts";
|
|
3
4
|
import { type PlanStore } from "./state.ts";
|
|
4
5
|
export interface ExitPlanDeps {
|
|
5
6
|
store: PlanStore;
|
|
@@ -14,5 +15,12 @@ export interface ExitPlanDeps {
|
|
|
14
15
|
enabled: boolean;
|
|
15
16
|
max: number;
|
|
16
17
|
};
|
|
18
|
+
/**
|
|
19
|
+
* Where approved acceptance criteria go (`ProofStore` fits). Without it the `acceptanceCriteria`
|
|
20
|
+
* field is not offered and is ignored: a core without Proof plans exactly as before.
|
|
21
|
+
*/
|
|
22
|
+
proof?: {
|
|
23
|
+
addCriterion(input: NewCriterion): unknown;
|
|
24
|
+
};
|
|
17
25
|
}
|
|
18
26
|
export declare function registerExitPlan(registry: ToolRegistry, deps: ExitPlanDeps): void;
|