@voybio/ace-swarm 2.4.8 → 2.4.10
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 +52 -14
- package/README.md +14 -10
- package/assets/.agents/ACE/ACE_coders/AGENTS.md +23 -30
- package/assets/.agents/ACE/ACE_coders/INSTRUCTIONS.md +21 -30
- package/assets/.agents/skills/landing-review-watcher/SKILL.md +1 -1
- package/assets/.agents/skills/learn-from-work/SKILL.md +41 -0
- package/assets/agent-state/ACE_WORKFLOW.md +25 -2
- package/assets/agent-state/MODULES/schemas/ACE_RUNTIME_PROFILE.schema.json +143 -2
- package/assets/agent-state/MODULES/schemas/RUNTIME_EXECUTOR_SESSION_REGISTRY.schema.json +2 -2
- package/assets/agent-state/MODULES/schemas/SWARM_HANDOFF.schema.json +10 -0
- package/assets/eval-battery/battery.json +1 -1
- package/assets/instructions/ACE_Coder.instructions.md +9 -13
- package/assets/scripts/ace-hook-dispatch.mjs +16 -411
- package/assets/tasks/SWARM_HANDOFF.example_ui_to_coders.json +1 -2
- package/assets/vendor/README.md +9 -0
- package/assets/vendor/vericify-reader.mjs +272 -0
- package/assets/vendor/vericify-reader.provenance.json +10 -0
- package/dist/ace-autonomy.d.ts +58 -0
- package/dist/ace-autonomy.js +211 -6
- package/dist/ace-internal-tools.d.ts +1 -0
- package/dist/ace-internal-tools.js +3 -3
- package/dist/ace-server-instructions.d.ts +3 -3
- package/dist/ace-server-instructions.js +46 -192
- package/dist/cli.js +44 -6
- package/dist/experience-reflector.d.ts +7 -0
- package/dist/experience-reflector.js +39 -0
- package/dist/goal-engine.d.ts +64 -7
- package/dist/goal-engine.js +197 -41
- package/dist/handoff-registry.d.ts +10 -0
- package/dist/handoff-registry.js +59 -0
- package/dist/helpers/artifacts.d.ts +6 -2
- package/dist/helpers/artifacts.js +8 -6
- package/dist/helpers/bootstrap.js +13 -20
- package/dist/helpers/path-utils.js +2 -0
- package/dist/helpers/store-resolution.js +18 -3
- package/dist/helpers/workspace-root.d.ts +2 -0
- package/dist/helpers/workspace-root.js +9 -0
- package/dist/helpers.d.ts +1 -0
- package/dist/helpers.js +1 -0
- package/dist/hermes/bridge-protocol.d.ts +1 -1
- package/dist/hermes/session-manager.d.ts +17 -0
- package/dist/hermes/session-manager.js +67 -14
- package/dist/host-goal-engine.d.ts +138 -0
- package/dist/host-goal-engine.js +793 -0
- package/dist/host-goal-status.d.ts +113 -0
- package/dist/host-goal-status.js +175 -0
- package/dist/host-learning.d.ts +68 -0
- package/dist/host-learning.js +115 -0
- package/dist/host-observation.d.ts +12 -0
- package/dist/host-observation.js +91 -0
- package/dist/host-plan-revision.d.ts +14 -0
- package/dist/host-plan-revision.js +101 -0
- package/dist/host-verification-job.d.ts +12 -0
- package/dist/host-verification-job.js +183 -0
- package/dist/host-work-context.d.ts +12 -0
- package/dist/host-work-context.js +8 -0
- package/dist/job-scheduler.d.ts +58 -13
- package/dist/job-scheduler.js +298 -48
- package/dist/job-worker.d.ts +3 -1
- package/dist/job-worker.js +10 -3
- package/dist/kanban.d.ts +4 -1
- package/dist/kanban.js +44 -16
- package/dist/learned-artifacts.d.ts +64 -0
- package/dist/learned-artifacts.js +321 -0
- package/dist/learning-controls.d.ts +142 -0
- package/dist/learning-controls.js +135 -0
- package/dist/learning-input-context.d.ts +9 -0
- package/dist/learning-input-context.js +62 -0
- package/dist/learning-lifecycle.d.ts +10 -0
- package/dist/learning-lifecycle.js +11 -0
- package/dist/learning-overlays.d.ts +3 -0
- package/dist/learning-overlays.js +17 -0
- package/dist/learning-policy.d.ts +40 -0
- package/dist/learning-policy.js +38 -0
- package/dist/learning-records.d.ts +35 -0
- package/dist/learning-records.js +17 -0
- package/dist/learning-scorecard.d.ts +82 -0
- package/dist/learning-scorecard.js +86 -0
- package/dist/learning-serving.d.ts +29 -0
- package/dist/learning-serving.js +134 -0
- package/dist/linked-work.d.ts +9 -0
- package/dist/linked-work.js +28 -0
- package/dist/local-model-runtime.js +1 -1
- package/dist/mcp-command-progress.d.ts +16 -0
- package/dist/mcp-command-progress.js +70 -0
- package/dist/model-bridge.d.ts +14 -13
- package/dist/model-bridge.js +73 -41
- package/dist/orchestrator-supervisor.d.ts +111 -6
- package/dist/orchestrator-supervisor.js +67 -43
- package/dist/plan-proposal.js +3 -3
- package/dist/playbook-curator.d.ts +1 -0
- package/dist/playbook-curator.js +11 -9
- package/dist/process-identity.d.ts +8 -0
- package/dist/process-identity.js +38 -0
- package/dist/progress-command.d.ts +34 -0
- package/dist/progress-command.js +153 -0
- package/dist/prompts.js +15 -15
- package/dist/resources.js +2 -2
- package/dist/runtime-command.d.ts +8 -1
- package/dist/runtime-command.js +48 -7
- package/dist/runtime-executor.js +33 -11
- package/dist/runtime-profile-syntax.d.ts +6 -0
- package/dist/runtime-profile-syntax.js +121 -0
- package/dist/runtime-profile.d.ts +37 -5
- package/dist/runtime-profile.js +36 -129
- package/dist/runtime-tool-specs.js +1 -1
- package/dist/safe-edit.d.ts +7 -3
- package/dist/safe-edit.js +40 -47
- package/dist/schemas.js +4 -2
- package/dist/server.d.ts +2 -16
- package/dist/server.js +14 -20
- package/dist/shared.d.ts +2 -0
- package/dist/shared.js +2 -0
- package/dist/skill-auditor.d.ts +19 -0
- package/dist/skill-auditor.js +32 -2
- package/dist/skill-catalog.d.ts +4 -1
- package/dist/skill-catalog.js +6 -5
- package/dist/store/ace-packed-store.d.ts +5 -1
- package/dist/store/ace-packed-store.js +33 -1
- package/dist/store/bootstrap-store.js +19 -12
- package/dist/store/cache-workspace.d.ts +8 -0
- package/dist/store/cache-workspace.js +94 -27
- package/dist/store/importer.js +11 -20
- package/dist/store/knowledge-bake.d.ts +3 -1
- package/dist/store/knowledge-bake.js +10 -1
- package/dist/store/legacy-vericify-posts.d.ts +8 -0
- package/dist/store/legacy-vericify-posts.js +76 -0
- package/dist/store/materializers/hook-context-materializer.d.ts +2 -1
- package/dist/store/materializers/hook-context-materializer.js +6 -2
- package/dist/store/materializers/host-file-materializer.js +2 -2
- package/dist/store/materializers/projection-manager.d.ts +4 -1
- package/dist/store/materializers/projection-manager.js +10 -3
- package/dist/store/materializers/todo-syncer.d.ts +39 -20
- package/dist/store/materializers/todo-syncer.js +208 -110
- package/dist/store/materializers/vericify-projector.js +15 -26
- package/dist/store/repositories/goal-repository.d.ts +51 -2
- package/dist/store/repositories/goal-repository.js +114 -48
- package/dist/store/repositories/playbook-repository.d.ts +41 -3
- package/dist/store/repositories/playbook-repository.js +61 -5
- package/dist/store/repositories/todo-repository.d.ts +8 -2
- package/dist/store/repositories/todo-repository.js +1 -1
- package/dist/store/repositories/vericify-repository.d.ts +4 -0
- package/dist/store/repositories/vericify-repository.js +46 -0
- package/dist/store/skills-install.d.ts +1 -0
- package/dist/store/skills-install.js +20 -16
- package/dist/store/source-preservation.d.ts +24 -0
- package/dist/store/source-preservation.js +79 -0
- package/dist/store/store-artifacts.js +8 -1
- package/dist/store/test-write-boundary.d.ts +16 -0
- package/dist/store/test-write-boundary.js +175 -0
- package/dist/store/types.d.ts +5 -1
- package/dist/store/write-coordinator.js +16 -0
- package/dist/task-plan-context.d.ts +11 -0
- package/dist/task-plan-context.js +59 -0
- package/dist/todo-state.d.ts +19 -9
- package/dist/todo-state.js +204 -321
- package/dist/tools-agent.d.ts +1 -1
- package/dist/tools-agent.js +38 -62
- package/dist/tools-files.d.ts +0 -3
- package/dist/tools-files.js +21 -11
- package/dist/tools-framework.js +16 -25
- package/dist/tools-goal.d.ts +2 -4
- package/dist/tools-goal.js +119 -16
- package/dist/tools-handoff.d.ts +3 -2
- package/dist/tools-handoff.js +53 -23
- package/dist/tools-lifecycle.js +1 -1
- package/dist/tools-memory.d.ts +0 -3
- package/dist/tools-memory.js +51 -6
- package/dist/tools-scheduler.js +20 -17
- package/dist/tools-todo.js +160 -148
- package/dist/vericify-bridge.d.ts +8 -0
- package/dist/vericify-bridge.js +18 -0
- package/dist/vericify-context.d.ts +4 -0
- package/dist/vericify-context.js +26 -5
- package/dist/vericify-native.d.ts +101 -0
- package/dist/vericify-native.js +221 -0
- package/dist/verification-worker.d.ts +2 -0
- package/dist/verification-worker.js +31 -0
- package/docs/mcp-tool-reference.md +50 -10
- package/docs/troubleshooting.md +29 -0
- package/package.json +19 -6
- package/scripts/dev/run-tests-isolated.mjs +165 -0
- package/scripts/dev/run_hermes_crispr_live.mjs +1 -0
- package/scripts/dev/test-isolation-guard.mjs +20 -0
- package/scripts/dev/test-preload.mjs +153 -0
- package/scripts/dev/test-source-inventory.mjs +31 -0
- package/docs/development.md +0 -46
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/** Read-only host status. Full records remain in the existing goal/plan owners. */
|
|
2
|
+
import { type GoalEngineDeps } from './goal-engine.js';
|
|
3
|
+
export declare function getCompactGoalStatus(goalId: string, agent?: string, deps?: GoalEngineDeps): {
|
|
4
|
+
goal_id: string;
|
|
5
|
+
status: import("./goal-engine.js").GoalStatus;
|
|
6
|
+
verified: boolean;
|
|
7
|
+
objective: string;
|
|
8
|
+
now: string;
|
|
9
|
+
current_step: {
|
|
10
|
+
age_ms?: number | undefined;
|
|
11
|
+
last_ace_call_at?: string | undefined;
|
|
12
|
+
contact_stale?: boolean | undefined;
|
|
13
|
+
observation?: {
|
|
14
|
+
status: "pending" | "passed" | "unmet" | "unsupported";
|
|
15
|
+
requirement: "action" | "command_pass" | undefined;
|
|
16
|
+
ref_count: number;
|
|
17
|
+
reason_code: string | undefined;
|
|
18
|
+
} | undefined;
|
|
19
|
+
step_id: string;
|
|
20
|
+
title: string;
|
|
21
|
+
status: import("./orchestrator-supervisor.js").TaskStepStatus;
|
|
22
|
+
attempt: number | undefined;
|
|
23
|
+
assignee: string | undefined;
|
|
24
|
+
} | undefined;
|
|
25
|
+
next_ready: {
|
|
26
|
+
age_ms?: number | undefined;
|
|
27
|
+
last_ace_call_at?: string | undefined;
|
|
28
|
+
contact_stale?: boolean | undefined;
|
|
29
|
+
observation?: {
|
|
30
|
+
status: "pending" | "passed" | "unmet" | "unsupported";
|
|
31
|
+
requirement: "action" | "command_pass" | undefined;
|
|
32
|
+
ref_count: number;
|
|
33
|
+
reason_code: string | undefined;
|
|
34
|
+
} | undefined;
|
|
35
|
+
step_id: string;
|
|
36
|
+
title: string;
|
|
37
|
+
status: import("./orchestrator-supervisor.js").TaskStepStatus;
|
|
38
|
+
attempt: number | undefined;
|
|
39
|
+
assignee: string | undefined;
|
|
40
|
+
}[];
|
|
41
|
+
next_ready_count: number;
|
|
42
|
+
blocked: {
|
|
43
|
+
reason: string;
|
|
44
|
+
age_ms?: number | undefined;
|
|
45
|
+
last_ace_call_at?: string | undefined;
|
|
46
|
+
contact_stale?: boolean | undefined;
|
|
47
|
+
observation?: {
|
|
48
|
+
status: "pending" | "passed" | "unmet" | "unsupported";
|
|
49
|
+
requirement: "action" | "command_pass" | undefined;
|
|
50
|
+
ref_count: number;
|
|
51
|
+
reason_code: string | undefined;
|
|
52
|
+
} | undefined;
|
|
53
|
+
step_id: string;
|
|
54
|
+
title: string;
|
|
55
|
+
status: import("./orchestrator-supervisor.js").TaskStepStatus;
|
|
56
|
+
attempt: number | undefined;
|
|
57
|
+
assignee: string | undefined;
|
|
58
|
+
}[];
|
|
59
|
+
blocked_count: number;
|
|
60
|
+
waiting_on: string[];
|
|
61
|
+
verification: {
|
|
62
|
+
failed_command_count: number;
|
|
63
|
+
failed_commands: {
|
|
64
|
+
command: string;
|
|
65
|
+
output_tail: string;
|
|
66
|
+
}[];
|
|
67
|
+
missing_artifact_count: number;
|
|
68
|
+
missing_artifacts: string[];
|
|
69
|
+
missing_evidence_count: number;
|
|
70
|
+
run?: {
|
|
71
|
+
command?: {
|
|
72
|
+
status: "done" | "running" | "failed" | "suspected_stall" | "terminating";
|
|
73
|
+
pid: number | undefined;
|
|
74
|
+
command: string;
|
|
75
|
+
elapsed_ms: number;
|
|
76
|
+
silence_ms: number;
|
|
77
|
+
output_tail: string;
|
|
78
|
+
last_output_at: string | undefined;
|
|
79
|
+
} | undefined;
|
|
80
|
+
job_id: string;
|
|
81
|
+
status: "done" | "running" | "failed" | "stopped" | "interrupted" | "queued" | "uncertain";
|
|
82
|
+
attempt: number;
|
|
83
|
+
worker: import("./process-identity.js").ProcessIdentity | undefined;
|
|
84
|
+
worker_state: "unknown" | "alive" | "dead" | undefined;
|
|
85
|
+
command_state: "unknown" | "alive" | "dead" | undefined;
|
|
86
|
+
elapsed_ms: number;
|
|
87
|
+
} | undefined;
|
|
88
|
+
status: string;
|
|
89
|
+
};
|
|
90
|
+
pending_follow_up_count: number;
|
|
91
|
+
message: string | undefined;
|
|
92
|
+
options: {
|
|
93
|
+
tool: string;
|
|
94
|
+
args: Record<string, unknown>;
|
|
95
|
+
why: string;
|
|
96
|
+
}[];
|
|
97
|
+
operator_proposal: {
|
|
98
|
+
tool: string;
|
|
99
|
+
args: Record<string, unknown>;
|
|
100
|
+
requires_operator_approval: true;
|
|
101
|
+
why: string;
|
|
102
|
+
} | undefined;
|
|
103
|
+
detail: {
|
|
104
|
+
tool: string;
|
|
105
|
+
args: {
|
|
106
|
+
goal_id: string;
|
|
107
|
+
view: string;
|
|
108
|
+
};
|
|
109
|
+
why: string;
|
|
110
|
+
};
|
|
111
|
+
previews_truncated: boolean;
|
|
112
|
+
} | undefined;
|
|
113
|
+
//# sourceMappingURL=host-goal-status.d.ts.map
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/** Read-only host status. Full records remain in the existing goal/plan owners. */
|
|
2
|
+
import { resolveGoalDeps } from './goal-engine.js';
|
|
3
|
+
import { resolveCurrentWork, formatCurrentWork } from './ace-autonomy.js';
|
|
4
|
+
import { getReadyTaskSteps, utf8Prefix } from './task-plan-context.js';
|
|
5
|
+
import { hostVerificationActive } from './host-verification-job.js';
|
|
6
|
+
import { probeProcess } from './process-identity.js';
|
|
7
|
+
const preview = (value, bytes) => utf8Prefix(value.replace(/\s+/g, ' ').trim(), bytes);
|
|
8
|
+
function stepView(step, now) {
|
|
9
|
+
return { step_id: step.step_id, title: preview(step.task.split(/\r?\n/)[0], 160),
|
|
10
|
+
status: step.status, attempt: step.attempt, assignee: step.assignee,
|
|
11
|
+
...(step.observation_check ? { observation: { status: step.observation_check.status, requirement: step.required_observation, ref_count: step.observation_refs?.length ?? 0, reason_code: step.observation_check.reason_code } } : {}),
|
|
12
|
+
...(step.started_at ? { age_ms: Math.max(0, now - Date.parse(step.started_at)), last_ace_call_at: step.last_ace_call_at ?? step.started_at,
|
|
13
|
+
contact_stale: now - Date.parse(step.last_ace_call_at ?? step.started_at) >= 10 * 60000 } : {}) };
|
|
14
|
+
}
|
|
15
|
+
export function getCompactGoalStatus(goalId, agent, deps = {}) {
|
|
16
|
+
const d = resolveGoalDeps(deps);
|
|
17
|
+
const goal = d.repo.getGoal(goalId), plan = d.repo.getPlan(goalId);
|
|
18
|
+
if (!goal)
|
|
19
|
+
return undefined;
|
|
20
|
+
const steps = plan?.steps ?? [];
|
|
21
|
+
const ready = plan ? getReadyTaskSteps(plan) : [];
|
|
22
|
+
const running = steps.filter(s => s.status === 'running');
|
|
23
|
+
const blocked = steps.filter(s => ['blocked', 'failed'].includes(s.status));
|
|
24
|
+
const failed = (goal.verification_results ?? []).filter(r => !r.ok);
|
|
25
|
+
const check = goal.final_contract_check;
|
|
26
|
+
const workerState = goal.verification_run?.worker ? probeProcess(goal.verification_run.worker) : undefined;
|
|
27
|
+
const commandState = goal.verification_run?.command?.process ? probeProcess(goal.verification_run.command.process) : undefined;
|
|
28
|
+
const waiting = [...new Set(blocked.flatMap(s => s.waiting_on ? [s.waiting_on] : []))];
|
|
29
|
+
const options = [];
|
|
30
|
+
let operatorProposal;
|
|
31
|
+
let message;
|
|
32
|
+
if (goal.stop_requested || goal.status === 'stopped') {
|
|
33
|
+
waiting.push('user:resume');
|
|
34
|
+
message = 'Stopped. Resume only when requested.';
|
|
35
|
+
}
|
|
36
|
+
else if (goal.executor !== 'host') {
|
|
37
|
+
const budgetKind = goal.reason_code?.startsWith('goal_budget_exhausted:') ? goal.reason_code.slice('goal_budget_exhausted:'.length) : undefined;
|
|
38
|
+
const budgetExplicit = budgetKind === 'max_wall_clock' ? goal.budgets.deadline_explicit
|
|
39
|
+
: budgetKind === 'max_replans' ? goal.budgets.replan_budget_explicit
|
|
40
|
+
: budgetKind === 'max_ticks' ? goal.budgets.tick_budget_explicit : false;
|
|
41
|
+
if (budgetKind && budgetExplicit) {
|
|
42
|
+
waiting.push('operator:budget');
|
|
43
|
+
message = 'Accepted work is retained. Obtain operator approval before increasing an engine allowance; do not execute a proposal automatically.';
|
|
44
|
+
if (budgetKind === 'max_wall_clock') {
|
|
45
|
+
const hours = Math.ceil(Math.max(goal.budgets.max_wall_clock_ms, d.now() - Date.parse(goal.started_at ?? goal.created_at)) / 3600000) + 1;
|
|
46
|
+
operatorProposal = { tool: 'resume_goal', args: { goal_id: goalId, budget_hours: hours }, requires_operator_approval: true, why: 'Ask the operator to approve this total deadline from the original start before submitting it.' };
|
|
47
|
+
}
|
|
48
|
+
else if (budgetKind === 'max_replans') {
|
|
49
|
+
operatorProposal = { tool: 'resume_goal', args: { goal_id: goalId, max_replans: Math.max(goal.budgets.max_replans, goal.replan_count) + 1 }, requires_operator_approval: true, why: 'Ask the operator to approve this total replan allowance before submitting it.' };
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
message = 'The explicit max_ticks allowance is exhausted. resume_goal does not accept a max_ticks increase; inspect full status and ask the operator to choose how to continue.';
|
|
53
|
+
options.push({ tool: 'get_goal_status', args: { goal_id: goalId, view: 'full' }, why: 'Inspect the explicit tick allowance and retained work before an operator decision.' });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else if (goal.status === 'executing' || goal.status === 'verifying') {
|
|
57
|
+
waiting.push('engine:execution');
|
|
58
|
+
message = 'Engine work may still be in flight. Read its status; do not start another runner from this view.';
|
|
59
|
+
options.push({ tool: 'get_goal_status', args: { goal_id: goalId, view: 'compact' }, why: 'Observe the existing engine run without assigning host steps or duplicating work.' });
|
|
60
|
+
}
|
|
61
|
+
else if (goal.reason_code === 'contract_uncheckable') {
|
|
62
|
+
waiting.push('operator:contract');
|
|
63
|
+
message = 'This success contract cannot be checked. Resume cannot repair it; inspect the retained contract before starting a goal with an artifact or verification command.';
|
|
64
|
+
options.push({ tool: 'get_goal_status', args: { goal_id: goalId, view: 'full' }, why: 'Inspect the uncheckable contract and retained work.' });
|
|
65
|
+
}
|
|
66
|
+
else if (goal.reason_code === 'repeated_failure') {
|
|
67
|
+
waiting.push('operator:repeated_failure');
|
|
68
|
+
message = goal.result_summary;
|
|
69
|
+
options.push({ tool: 'get_goal_status', args: { goal_id: goalId, view: 'full' }, why: 'Inspect repeated unchanged failures before explicitly resuming the engine.' });
|
|
70
|
+
}
|
|
71
|
+
else if (['draft', 'planning', 'blocked'].includes(goal.status)) {
|
|
72
|
+
options.push({ tool: 'resume_goal', args: { goal_id: goalId }, why: budgetKind ? 'Clear an obsolete implicit-budget blocker without increasing an allowance.' : 'Resume the engine plan after addressing its reported blocker; accepted steps remain complete.' });
|
|
73
|
+
}
|
|
74
|
+
else if (goal.status === 'failed') {
|
|
75
|
+
options.push({ tool: 'get_goal_status', args: { goal_id: goalId, view: 'full' }, why: 'Inspect the failed engine result before an operator decides how to continue.' });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else if (goal.reason_code?.startsWith('follow-up budget reached')) {
|
|
79
|
+
waiting.push('operator:replan_budget');
|
|
80
|
+
message = 'Follow-up admission needs an authorized replan budget; pending work is retained in full status.';
|
|
81
|
+
if (goal.pending_follow_ups?.length)
|
|
82
|
+
operatorProposal = { tool: 'resume_goal', args: { goal_id: goalId, max_replans: Math.max(goal.budgets.max_replans + 1, goal.replan_count + goal.pending_follow_ups.length) }, requires_operator_approval: true, why: 'Ask the operator to approve this total replan allowance before submitting it. Pending work is retained.' };
|
|
83
|
+
else {
|
|
84
|
+
message = 'No pending follow-up batch is available to admit. Inspect the retained requirement-change receipt; increasing a budget alone does not apply that change.';
|
|
85
|
+
options.push({ tool: 'get_goal_status', args: { goal_id: goalId, view: 'full' }, why: 'Inspect the different retained obligation before deciding how to revise it.' });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else if (goal.reason_code === 'host_budget_exhausted') {
|
|
89
|
+
waiting.push('operator:budget');
|
|
90
|
+
message = 'Outcomes are retained. Obtain operator approval before increasing budget_hours; do not execute the proposal automatically.';
|
|
91
|
+
const hours = Math.ceil(Math.max(goal.budgets.max_wall_clock_ms, d.now() - Date.parse(goal.started_at ?? goal.created_at)) / 3600000) + 1;
|
|
92
|
+
if (goal.budgets.host_deadline_explicit)
|
|
93
|
+
operatorProposal = { tool: 'resume_goal', args: { goal_id: goalId, budget_hours: hours }, requires_operator_approval: true, why: 'Ask the operator to approve this total hour allowance before submitting it. Accepted work is retained.' };
|
|
94
|
+
else {
|
|
95
|
+
message = 'This legacy blocker has no explicit deadline. Resume clears it without increasing any budget.';
|
|
96
|
+
options.push({ tool: 'resume_goal', args: { goal_id: goalId }, why: 'Clear a legacy default-deadline blocker; this goal has no explicit deadline.' });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else if (goal.status === 'verifying') {
|
|
100
|
+
if (hostVerificationActive(d.workspaceRoot, goalId)) {
|
|
101
|
+
const uncertain = goal.verification_run?.status === 'uncertain' || workerState === 'unknown' || workerState === 'dead';
|
|
102
|
+
waiting.push(uncertain ? 'operator:verification_recovery' : `verification:${goal.verification_run?.job_id ?? 'in-process'}`);
|
|
103
|
+
message = uncertain ? 'Verification ownership needs inspection. A live or uncertain command is retained; do not rerun it. Inspect full status, and resume only after old processes are confirmed gone.' : 'Final acceptance is running. Retain this execution; do not start another check.';
|
|
104
|
+
options.push({ tool: 'get_goal_status', args: { goal_id: goalId, view: 'compact', ...(agent ? { agent } : {}) }, why: 'Wait for the existing verification job and read its progress.' });
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
message = 'Final verification was interrupted. Resume reruns only the checks after confirming the old processes are gone.';
|
|
108
|
+
options.push({ tool: 'resume_goal', args: { goal_id: goalId }, why: 'Recover interrupted verification without rerunning accepted host steps.' });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else if (goal.reason_code === 'repeated_failure') {
|
|
112
|
+
waiting.push('operator:repeated_failure');
|
|
113
|
+
message = goal.result_summary;
|
|
114
|
+
options.push({ tool: 'get_goal_status', args: { goal_id: goalId, view: 'full' }, why: 'Inspect repeated unchanged failures before explicitly resuming.' });
|
|
115
|
+
}
|
|
116
|
+
else if (goal.reason_code === 'observation_unmet') {
|
|
117
|
+
message = goal.result_summary;
|
|
118
|
+
options.push({ tool: 'resume_goal', args: { goal_id: goalId }, why: 'Retry evidence collection for the original accepted attempt; do not repeat the edit.' });
|
|
119
|
+
}
|
|
120
|
+
else if (blocked.some(s => s.observation_check?.status === 'unsupported')) {
|
|
121
|
+
message = 'The configured observer cannot prove the declared requirement. Inspect its capability explanation; Unknown file attribution is not a passed command or an ACE attempt link.';
|
|
122
|
+
options.push({ tool: 'get_goal_status', args: { goal_id: goalId, view: 'full' }, why: 'Inspect the unsupported observation requirement and retained outcome before changing configuration.' });
|
|
123
|
+
}
|
|
124
|
+
else if (goal.status !== 'done') {
|
|
125
|
+
if (ready.length || running.some(s => !agent || s.assignee === agent)) {
|
|
126
|
+
options.push({ tool: 'next_step', args: { goal_id: goalId, ...(agent ? { agent } : {}) }, why: 'Receive eligible corrective work or resume the existing assignment.' });
|
|
127
|
+
}
|
|
128
|
+
else if (blocked.some(s => !s.waiting_on)) {
|
|
129
|
+
options.push({ tool: 'resume_goal', args: { goal_id: goalId }, why: 'After correcting the reported blocker, make a new attempt eligible.' });
|
|
130
|
+
}
|
|
131
|
+
else if (running.length)
|
|
132
|
+
waiting.push(`agent:${preview(running[0].assignee ?? 'current owner', 120)}`);
|
|
133
|
+
}
|
|
134
|
+
const verificationStatus = goal.status === 'verifying' ? 'pending'
|
|
135
|
+
: failed.length || check?.ok === false ? 'failed'
|
|
136
|
+
: goal.verified ? 'passed' : check?.ok ? 'artifacts_checked' : 'not_required';
|
|
137
|
+
const result = {
|
|
138
|
+
goal_id: goalId, status: goal.status, verified: goal.verified ?? false,
|
|
139
|
+
objective: preview(goal.objective, 240),
|
|
140
|
+
now: formatCurrentWork(resolveCurrentWork({ workspaceRoot: d.workspaceRoot, goal_id: goalId, agent }), 'line'),
|
|
141
|
+
current_step: running[0] ? stepView(running[0], d.now()) : undefined,
|
|
142
|
+
next_ready: ready.slice(0, 3).map(s => stepView(s, d.now())), next_ready_count: ready.length,
|
|
143
|
+
blocked: blocked.slice(0, 3).map(s => ({ ...stepView(s, d.now()), reason: preview(s.blocked_reason ?? s.result_summary ?? '', 180) })),
|
|
144
|
+
blocked_count: blocked.length, waiting_on: waiting.slice(0, 3).map(s => preview(s, 160)),
|
|
145
|
+
verification: { status: verificationStatus,
|
|
146
|
+
...(goal.verification_run ? { run: { job_id: goal.verification_run.job_id, status: goal.verification_run.status,
|
|
147
|
+
attempt: goal.verification_run.attempt, worker: goal.verification_run.worker, worker_state: workerState, command_state: commandState,
|
|
148
|
+
elapsed_ms: Math.max(0, d.now() - Date.parse(goal.verification_run.enqueued_at)),
|
|
149
|
+
...(goal.verification_run.command ? { command: { status: goal.verification_run.command.status,
|
|
150
|
+
pid: goal.verification_run.command.pid, command: preview(goal.verification_run.command.command, 160),
|
|
151
|
+
elapsed_ms: Math.max(0, d.now() - Date.parse(goal.verification_run.command.started_at)),
|
|
152
|
+
silence_ms: Math.max(0, d.now() - Date.parse(goal.verification_run.command.last_progress_at)),
|
|
153
|
+
output_tail: utf8Prefix(goal.verification_run.command.output_tail.slice(-600), 500),
|
|
154
|
+
last_output_at: goal.verification_run.command.last_output_at } } : {}) } } : {}), failed_command_count: failed.length,
|
|
155
|
+
failed_commands: failed.slice(0, 2).map(r => ({ command: preview(r.command, 160), output_tail: utf8Prefix(r.output.slice(-600), 300) })),
|
|
156
|
+
missing_artifact_count: check?.missing_artifacts.length ?? 0,
|
|
157
|
+
missing_artifacts: check?.missing_artifacts.slice(0, 3).map(p => preview(p, 160)) ?? [],
|
|
158
|
+
missing_evidence_count: check?.missing_evidence_refs.length ?? 0 },
|
|
159
|
+
pending_follow_up_count: goal.pending_follow_ups?.length ?? 0,
|
|
160
|
+
message, options, operator_proposal: operatorProposal,
|
|
161
|
+
detail: { tool: 'get_goal_status', args: { goal_id: goalId, view: 'full' }, why: 'Retrieve complete plan, attempts and verification evidence.' },
|
|
162
|
+
previews_truncated: false,
|
|
163
|
+
};
|
|
164
|
+
// Keep counts, the primary failure, executable actions and full-record locator.
|
|
165
|
+
// Only optional previews shrink when several dense categories coexist.
|
|
166
|
+
const bytes = () => Buffer.byteLength(JSON.stringify(result));
|
|
167
|
+
for (const previews of [result.next_ready, result.blocked, result.verification.missing_artifacts, result.verification.failed_commands]) {
|
|
168
|
+
while (bytes() > 4096 && previews.length > 1) {
|
|
169
|
+
previews.pop();
|
|
170
|
+
result.previews_truncated = true;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return result;
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=host-goal-status.js.map
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { TaskStep, StepAttempt } from './orchestrator-supervisor.js';
|
|
2
|
+
import { type GoalRecord } from './store/repositories/goal-repository.js';
|
|
3
|
+
import { type PlaybookScope } from './store/repositories/playbook-repository.js';
|
|
4
|
+
export interface Episode {
|
|
5
|
+
id: string;
|
|
6
|
+
attempt?: number;
|
|
7
|
+
goal_id: string;
|
|
8
|
+
step_id: string;
|
|
9
|
+
role: string;
|
|
10
|
+
task: string;
|
|
11
|
+
paths: string[];
|
|
12
|
+
tags: string[];
|
|
13
|
+
status: StepAttempt['status'];
|
|
14
|
+
summary: string;
|
|
15
|
+
failure_signature?: string;
|
|
16
|
+
failure_excerpt?: string;
|
|
17
|
+
evidence_refs: string[];
|
|
18
|
+
served?: string[];
|
|
19
|
+
at: string;
|
|
20
|
+
observed_runs?: StepAttempt['observed_runs'];
|
|
21
|
+
checks?: Array<{
|
|
22
|
+
command: string;
|
|
23
|
+
cwd: string;
|
|
24
|
+
ok: boolean;
|
|
25
|
+
}>;
|
|
26
|
+
scope_snapshot?: StepAttempt['scope_snapshot'];
|
|
27
|
+
served_context?: StepAttempt['served_context'];
|
|
28
|
+
provenance?: string;
|
|
29
|
+
}
|
|
30
|
+
export declare function appendAttempt(step: TaskStep, _goal: Pick<GoalRecord, 'goal_id'>, at: string, checks?: StepAttempt['checks']): void;
|
|
31
|
+
export declare function listEpisodes(root: string, options?: {
|
|
32
|
+
since?: string;
|
|
33
|
+
scope?: PlaybookScope;
|
|
34
|
+
}): Episode[];
|
|
35
|
+
export declare function compactEpisode(ep: Episode): Episode;
|
|
36
|
+
export declare function harvestStep(root: string, goal: GoalRecord, step: TaskStep): Promise<void>;
|
|
37
|
+
export interface HostLessonInput {
|
|
38
|
+
context: string;
|
|
39
|
+
failure_pattern: string;
|
|
40
|
+
guardrail: string;
|
|
41
|
+
verification_check: string;
|
|
42
|
+
evidence_refs?: string[];
|
|
43
|
+
step_ref?: {
|
|
44
|
+
goal_id: string;
|
|
45
|
+
step_id: string;
|
|
46
|
+
};
|
|
47
|
+
paths?: string[];
|
|
48
|
+
}
|
|
49
|
+
export declare function addHostLesson(root: string, input: HostLessonInput): Promise<{
|
|
50
|
+
bullet: import("./store/repositories/playbook-repository.js").PlaybookBullet;
|
|
51
|
+
options: {
|
|
52
|
+
tool: string;
|
|
53
|
+
args: {
|
|
54
|
+
context: string;
|
|
55
|
+
failure_pattern: string;
|
|
56
|
+
guardrail: string;
|
|
57
|
+
verification_check: string;
|
|
58
|
+
evidence_refs?: string[];
|
|
59
|
+
step_ref?: {
|
|
60
|
+
goal_id: string;
|
|
61
|
+
step_id: string;
|
|
62
|
+
};
|
|
63
|
+
paths?: string[];
|
|
64
|
+
};
|
|
65
|
+
why: string;
|
|
66
|
+
}[];
|
|
67
|
+
}>;
|
|
68
|
+
//# sourceMappingURL=host-learning.d.ts.map
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { utf8Prefix } from './task-plan-context.js';
|
|
2
|
+
import { parse as parseYaml } from 'yaml';
|
|
3
|
+
import { readLearningPolicy } from './learning-policy.js';
|
|
4
|
+
/** Learning over existing host goal plans and playbook records; no capture or model calls. */
|
|
5
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
6
|
+
import { resolve, isAbsolute, relative } from 'node:path';
|
|
7
|
+
import { GoalRepository } from './store/repositories/goal-repository.js';
|
|
8
|
+
import { withPlaybookTransaction } from './store/repositories/playbook-repository.js';
|
|
9
|
+
import { readStoreBlobSync, listStoreKeysSync } from './store/store-snapshot.js';
|
|
10
|
+
import { operationalArtifactKey } from './store/store-artifacts.js';
|
|
11
|
+
import { failureSignature, normalizedFailureLines, reflectFromStepOutcome, learningTaskText } from './experience-reflector.js';
|
|
12
|
+
import { curate } from './playbook-curator.js';
|
|
13
|
+
const tags = ['test', 'build', 'store', 'todo', 'handoff', 'scheduler', 'migration', 'docs', 'release', 'mcp', 'hook', 'schema'];
|
|
14
|
+
export function appendAttempt(step, _goal, at, checks) {
|
|
15
|
+
const runs = step.observation_refs?.filter(r => r.attribution === 'Linked' && r.run?.attribution === 'Linked').flatMap(r => r.run ? [{ ...r.run, record_id: r.id }] : []).slice(-20);
|
|
16
|
+
const failure = runs?.filter(r => r.outcome === 'failed').map(r => r.output_tail ?? '').join('\n') || (step.status !== 'done' ? step.blocked_reason ?? step.result_summary ?? '' : '');
|
|
17
|
+
const scope_snapshot = stepScope(step, step.evidence_refs ?? [], checks);
|
|
18
|
+
const served_context = step.served_learning ? { items: step.served_learning.items.map(({ id, kind, scope }) => ({ id, kind, scope: structuredClone(scope) })), rendered_bytes: Buffer.byteLength(step.served_learning.text) } : undefined;
|
|
19
|
+
const entry = { attempt: step.attempt ?? 0, status: step.status === 'done' ? 'done' : step.status === 'blocked' ? 'blocked' : 'failed', at, summary: (step.result_summary ?? '').slice(0, 600), checks, scope_snapshot, served_context, evidence_refs: step.evidence_refs ?? [], served: step.served_learning?.items.map(i => i.id), failure_signature: failureSignature(failure), failure_excerpt: normalizedFailureLines(failure).slice(0, 6).join('\n') || undefined, ...(runs?.length ? { observed_runs: runs } : {}) };
|
|
20
|
+
step.attempts_log = [...(step.attempts_log ?? []).filter(e => e.attempt !== entry.attempt), entry].slice(-5);
|
|
21
|
+
}
|
|
22
|
+
function stepScope(step, refs, checks) {
|
|
23
|
+
const paths = [...new Set([...(step.expected_artifacts ?? []).map(a => a.path), ...refs.filter(ref => !/^(ep:|ace:|goal:|https?:|vericify:)/.test(ref))].map(path => path.replace(/\\/g, '/').replace(/^\.\//, '')))];
|
|
24
|
+
const task = checks?.length ? checks.map(c => c.command).join('\n') : learningTaskText(step);
|
|
25
|
+
return { role: step.role, paths, task_tags: tags.filter(tag => task.toLowerCase().includes(tag)) };
|
|
26
|
+
}
|
|
27
|
+
function episode(goal, step, entry) {
|
|
28
|
+
const scope = entry.scope_snapshot ?? stepScope(step, entry.evidence_refs, entry.checks);
|
|
29
|
+
return { id: `ep:${goal}:${step.step_id}:${entry.attempt}`, goal_id: goal, step_id: step.step_id, role: scope.role, task: step.task.slice(0, 600), paths: scope.paths, tags: scope.task_tags, ...entry, summary: entry.summary.slice(0, 600), provenance: 'ACE plan attempt; external references retain their own attribution and retention' };
|
|
30
|
+
}
|
|
31
|
+
export function listEpisodes(root, options = {}) {
|
|
32
|
+
const goals = new GoalRepository(root), rows = new Map();
|
|
33
|
+
// Reading a playbook through its mutating legacy migration API is forbidden here.
|
|
34
|
+
const keys = listStoreKeysSync(root, operationalArtifactKey('agent-state/playbook/'));
|
|
35
|
+
for (const key of keys) {
|
|
36
|
+
try {
|
|
37
|
+
const record = JSON.parse(readStoreBlobSync(root, key) ?? '{}');
|
|
38
|
+
for (const ep of record.evidence_snapshots ?? [])
|
|
39
|
+
rows.set(ep.id, ep);
|
|
40
|
+
}
|
|
41
|
+
catch { /* malformed historical record is not an episode */ }
|
|
42
|
+
}
|
|
43
|
+
for (const key of listStoreKeysSync(root, 'knowledge/').filter(k => /(?:\/SKILL\.md|\/LEARNED\.md|\/references\/proposals\/[^/]+\.md)$/.test(k))) {
|
|
44
|
+
try {
|
|
45
|
+
const raw = readStoreBlobSync(root, key) ?? '';
|
|
46
|
+
const front = parseYaml(raw.match(/^---\n([\s\S]*?)\n---/)?.[1] ?? '');
|
|
47
|
+
for (const ep of front?.ace_learning?.evidence_snapshots ?? [])
|
|
48
|
+
if (!rows.has(ep.id))
|
|
49
|
+
rows.set(ep.id, ep);
|
|
50
|
+
}
|
|
51
|
+
catch { /* Retained external IDs remain unavailable; no fabricated readback. */ }
|
|
52
|
+
}
|
|
53
|
+
// Standalone workspaces retain the same playbook records on disk.
|
|
54
|
+
const legacy = resolve(root, 'agent-state/playbook');
|
|
55
|
+
if (existsSync(legacy))
|
|
56
|
+
for (const name of readdirSync(legacy).filter(n => n.endsWith('.json') && n !== '__index.json')) {
|
|
57
|
+
try {
|
|
58
|
+
const bullet = JSON.parse(readFileSync(resolve(legacy, name), 'utf8'));
|
|
59
|
+
for (const ep of bullet.evidence_snapshots ?? [])
|
|
60
|
+
if (!rows.has(ep.id))
|
|
61
|
+
rows.set(ep.id, ep);
|
|
62
|
+
}
|
|
63
|
+
catch { /* unavailable record remains unavailable */ }
|
|
64
|
+
}
|
|
65
|
+
for (const g of goals.listGoals())
|
|
66
|
+
for (const s of goals.getPlan(g.goal_id)?.steps ?? [])
|
|
67
|
+
for (const entry of s.attempts_log ?? [])
|
|
68
|
+
rows.set(`ep:${g.goal_id}:${s.step_id}:${entry.attempt}`, episode(g.goal_id, s, entry));
|
|
69
|
+
return [...rows.values()].filter(e => (!options.since || e.at >= options.since) && (!options.scope?.role || e.role === options.scope.role) && (!options.scope?.failure_signature || e.failure_signature === options.scope.failure_signature) && (!options.scope?.task_tags?.length || e.tags.some(t => options.scope.task_tags.includes(t))) && (!options.scope?.paths?.length || e.paths.some(p => options.scope.paths.some(q => p === q || p.startsWith(q + '/'))))).sort((a, b) => a.at.localeCompare(b.at));
|
|
70
|
+
}
|
|
71
|
+
export function compactEpisode(ep) {
|
|
72
|
+
// Full immutable command bodies stay with Vericify; pin only ACE work context.
|
|
73
|
+
const { observed_runs: _, ...compact } = ep;
|
|
74
|
+
return { ...compact, task: utf8Prefix(compact.task, 200), summary: utf8Prefix(compact.summary, 600), failure_excerpt: compact.failure_excerpt ? utf8Prefix(compact.failure_excerpt, 200) : undefined };
|
|
75
|
+
}
|
|
76
|
+
export async function harvestStep(root, goal, step) {
|
|
77
|
+
if (!readLearningPolicy(root).enabled)
|
|
78
|
+
return;
|
|
79
|
+
const all = listEpisodes(root), ops = reflectFromStepOutcome(step, goal);
|
|
80
|
+
const current = all.filter(e => e.goal_id === goal.goal_id && e.step_id === step.step_id).at(-1);
|
|
81
|
+
if (current?.status === 'blocked' && current.failure_signature) {
|
|
82
|
+
const blockers = all.filter(e => e.status === 'blocked' && e.failure_signature === current.failure_signature);
|
|
83
|
+
if (new Set(blockers.map(e => `${e.goal_id}:${e.step_id}`)).size >= 2)
|
|
84
|
+
ops.push({ type: 'ADD', section: 'strategies', status: 'quarantined', scope: { role: step.role, failure_signature: current.failure_signature, paths: current.paths.map(p => p.split('/').slice(0, 2).join('/')), task_tags: current.tags }, content: `Repeated blocker: ${current.failure_excerpt ?? current.summary}. Compare the cited blocked steps before selecting a recovery.`, evidence_refs: blockers.map(e => e.id), provenance: { source: 'harvest', run_id: `blocker:${current.failure_signature}` } });
|
|
85
|
+
}
|
|
86
|
+
if (!ops.length)
|
|
87
|
+
return;
|
|
88
|
+
await withPlaybookTransaction(root, async (repo) => {
|
|
89
|
+
const existing = repo.listBullets();
|
|
90
|
+
const fresh = ops.filter(op => op.type === 'ADD' && !existing.some(b => b.provenance.source === 'harvest' && b.provenance.run_id === op.provenance.run_id && b.scope.failure_signature === op.scope.failure_signature));
|
|
91
|
+
for (const op of fresh)
|
|
92
|
+
if (op.type === 'ADD')
|
|
93
|
+
op.evidence_snapshots = all.filter(e => op.evidence_refs.includes(e.id)).map(compactEpisode);
|
|
94
|
+
if (fresh.length)
|
|
95
|
+
await curate(repo, fresh, { workspaceRoot: root, emitEvent: false });
|
|
96
|
+
});
|
|
97
|
+
const { ensureLearningStep } = await import('./learned-artifacts.js');
|
|
98
|
+
await ensureLearningStep(root);
|
|
99
|
+
}
|
|
100
|
+
export async function addHostLesson(root, input) {
|
|
101
|
+
const episodes = input.step_ref ? listEpisodes(root).filter(e => e.goal_id === input.step_ref.goal_id && e.step_id === input.step_ref.step_id) : [];
|
|
102
|
+
const evidence = [...new Set([...(input.evidence_refs ?? []), ...episodes.map(e => e.id)])];
|
|
103
|
+
const resolves = (ref) => {
|
|
104
|
+
if (ref.startsWith('ep:'))
|
|
105
|
+
return listEpisodes(root).some(e => e.id === ref);
|
|
106
|
+
const p = resolve(root, ref);
|
|
107
|
+
if (isAbsolute(relative(root, p)) || relative(root, p).startsWith('..'))
|
|
108
|
+
return false;
|
|
109
|
+
return existsSync(p) || readStoreBlobSync(root, ref) !== undefined || readStoreBlobSync(root, operationalArtifactKey(ref)) !== undefined;
|
|
110
|
+
};
|
|
111
|
+
const active = episodes.some(e => e.failure_signature && e.status !== 'done') || (input.evidence_refs ?? []).some(resolves);
|
|
112
|
+
const bullet = await withPlaybookTransaction(root, repo => repo.createBullet({ section: 'failure_modes', scope: { paths: input.paths?.map(p => p.replace(/\\/g, '/').split('/').slice(0, 2).join('/')), failure_signature: episodes.find(e => e.failure_signature)?.failure_signature }, content: `${input.guardrail}\nWhy: ${input.failure_pattern}\nCheck: ${input.verification_check}`, evidence_refs: evidence, status: active ? 'active' : 'quarantined', provenance: { source: 'operator', note: input.context }, evidence_snapshots: episodes.map(compactEpisode), label: active ? 'checked' : undefined }));
|
|
113
|
+
return { bullet, options: active ? [] : [{ tool: 'add_lesson', args: { ...input }, why: 'Attach resolvable evidence_refs or step_ref to activate this lesson.' }] };
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=host-learning.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Evidence collection is acceptance work, never another execution of the host's edit. */
|
|
2
|
+
import type { GoalEngineDeps } from './goal-engine.js';
|
|
3
|
+
import type { TaskPlan, TaskStep } from './orchestrator-supervisor.js';
|
|
4
|
+
export declare function observationSatisfied(step: TaskStep, records: NonNullable<TaskStep['observation_refs']>): boolean;
|
|
5
|
+
export declare function pendingObservation(step: TaskStep): boolean;
|
|
6
|
+
export declare function recoverableObservation(step: TaskStep): boolean;
|
|
7
|
+
export declare function observationAdapter(root: string, deps: GoalEngineDeps): GoalEngineDeps['observations'];
|
|
8
|
+
export declare function retainObservation(step: TaskStep, result: Awaited<ReturnType<NonNullable<GoalEngineDeps['observations']>['readWindow']>>): void;
|
|
9
|
+
/** Uses the existing goal.verify worker and lease; no watcher, timer daemon or history store. */
|
|
10
|
+
export declare function settleHostObservations(goalId: string, generation: number | undefined, deps: GoalEngineDeps): Promise<boolean>;
|
|
11
|
+
export declare function observationReadyForChecks(plan: TaskPlan): boolean;
|
|
12
|
+
//# sourceMappingURL=host-observation.d.ts.map
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { resolveGoalDeps } from './goal-engine.js';
|
|
2
|
+
import { configuredObservationAdapter } from './vericify-context.js';
|
|
3
|
+
import { deriveTaskPlanStatus } from './orchestrator-supervisor.js';
|
|
4
|
+
import { withStoreWriteCoordinator } from './store/write-coordinator.js';
|
|
5
|
+
export function observationSatisfied(step, records) {
|
|
6
|
+
return records.some(r => r.attribution === 'Linked' && (step.required_observation === 'action' || (r.kind === 'command' && r.result === 'passed')));
|
|
7
|
+
}
|
|
8
|
+
export function pendingObservation(step) {
|
|
9
|
+
const check = step.observation_check;
|
|
10
|
+
return !!check && check.attempt === step.attempt && check.status === 'pending';
|
|
11
|
+
}
|
|
12
|
+
export function recoverableObservation(step) {
|
|
13
|
+
const check = step.observation_check;
|
|
14
|
+
return !!check && check.attempt === step.attempt && ['pending', 'unmet', 'unsupported'].includes(check.status);
|
|
15
|
+
}
|
|
16
|
+
export function observationAdapter(root, deps) {
|
|
17
|
+
return deps.observations ?? configuredObservationAdapter(root);
|
|
18
|
+
}
|
|
19
|
+
export function retainObservation(step, result) {
|
|
20
|
+
step.observation = result.status;
|
|
21
|
+
step.observation_refs = result.records;
|
|
22
|
+
if (result.query)
|
|
23
|
+
step.observation_query = result.query;
|
|
24
|
+
if (result.coverage)
|
|
25
|
+
step.observation_coverage = result.coverage;
|
|
26
|
+
}
|
|
27
|
+
/** Uses the existing goal.verify worker and lease; no watcher, timer daemon or history store. */
|
|
28
|
+
export async function settleHostObservations(goalId, generation, deps) {
|
|
29
|
+
const d = resolveGoalDeps(deps), adapter = observationAdapter(d.workspaceRoot, deps);
|
|
30
|
+
for (;;) {
|
|
31
|
+
const goal = d.repo.getGoal(goalId), plan = d.repo.getPlan(goalId);
|
|
32
|
+
if (!goal || !plan || goal.verification_attempt !== generation || goal.stop_requested || goal.status !== 'verifying')
|
|
33
|
+
return false;
|
|
34
|
+
const step = plan.steps.find(pendingObservation);
|
|
35
|
+
if (!step)
|
|
36
|
+
return true;
|
|
37
|
+
const window = step.observation_window && { ...step.observation_window };
|
|
38
|
+
let read = { status: 'unavailable', records: [], reason_code: 'observation_adapter_unavailable' };
|
|
39
|
+
if (adapter && window) {
|
|
40
|
+
try {
|
|
41
|
+
read = await adapter.readWindow(window);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
read = { status: 'unavailable', records: [], reason_code: 'observation_query_failed' };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
let wait = 0, settled = false;
|
|
48
|
+
await withStoreWriteCoordinator(d.repo.planPath(goalId), async () => {
|
|
49
|
+
const current = d.repo.getGoal(goalId), fresh = d.repo.getPlan(goalId), target = fresh?.steps.find(s => s.step_id === step.step_id);
|
|
50
|
+
if (!current || !fresh || !target || current.stop_requested || current.status !== 'verifying' || current.verification_attempt !== generation || target.attempt !== step.attempt || !pendingObservation(target) || JSON.stringify(target.observation_window) !== JSON.stringify(window)) {
|
|
51
|
+
settled = true;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
retainObservation(target, read);
|
|
55
|
+
const check = target.observation_check;
|
|
56
|
+
if (observationSatisfied(target, read.records)) {
|
|
57
|
+
check.status = 'passed';
|
|
58
|
+
check.reason_code = undefined;
|
|
59
|
+
target.status = 'done';
|
|
60
|
+
target.waiting_on = undefined;
|
|
61
|
+
target.blocked_reason = undefined;
|
|
62
|
+
}
|
|
63
|
+
else if (d.now() >= Date.parse(check.deadline_at) || !adapter) {
|
|
64
|
+
check.status = 'unmet';
|
|
65
|
+
check.reason_code = read.reason_code ?? 'required_observation_missing';
|
|
66
|
+
target.status = 'blocked';
|
|
67
|
+
target.waiting_on = 'operator:observation';
|
|
68
|
+
target.blocked_reason = `Required ${target.required_observation} observation is unmet (${check.reason_code}). Resume retries evidence collection for this accepted attempt; do not repeat the edit.`;
|
|
69
|
+
current.status = 'blocked';
|
|
70
|
+
current.reason_code = 'observation_unmet';
|
|
71
|
+
current.result_summary = target.blocked_reason;
|
|
72
|
+
if (current.verification_run)
|
|
73
|
+
current.verification_run.status = 'failed';
|
|
74
|
+
settled = true;
|
|
75
|
+
}
|
|
76
|
+
else
|
|
77
|
+
wait = Math.min(check.poll_ms, Math.max(1, Date.parse(check.deadline_at) - d.now()));
|
|
78
|
+
fresh.status = deriveTaskPlanStatus(fresh);
|
|
79
|
+
d.repo.savePlan(goalId, fresh);
|
|
80
|
+
d.repo.saveGoal(current);
|
|
81
|
+
}, { operation_label: 'host_observation_acceptance' });
|
|
82
|
+
if (settled)
|
|
83
|
+
return false;
|
|
84
|
+
if (wait)
|
|
85
|
+
await d.sleep(wait);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
export function observationReadyForChecks(plan) {
|
|
89
|
+
return plan.steps.every(s => ['done', 'skipped'].includes(s.status));
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=host-observation.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type TaskPlan, type TaskStep } from "./orchestrator-supervisor.js";
|
|
2
|
+
export type HostRequirementUpdate = {
|
|
3
|
+
step_id: string;
|
|
4
|
+
reason: string;
|
|
5
|
+
task?: string;
|
|
6
|
+
expected_artifacts?: TaskStep["expected_artifacts"];
|
|
7
|
+
required_evidence_refs?: string[];
|
|
8
|
+
};
|
|
9
|
+
/** Prepare a requirement change; the caller owns persistence and execution. */
|
|
10
|
+
export declare function reviseHostRequirements(plan: TaskPlan, updates: HostRequirementUpdate[]): {
|
|
11
|
+
plan: TaskPlan;
|
|
12
|
+
reopened: string[];
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=host-plan-revision.d.ts.map
|