@kernel.chat/kbot 3.99.20 → 3.99.22

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.
Files changed (36) hide show
  1. package/README.md +11 -0
  2. package/dist/agent.js +23 -0
  3. package/dist/agents/producer.js +65 -23
  4. package/dist/auth.d.ts +2 -0
  5. package/dist/cli.js +7 -4
  6. package/dist/critic-gate.d.ts +29 -0
  7. package/dist/critic-gate.js +223 -0
  8. package/dist/critic-retrospect.d.ts +64 -0
  9. package/dist/critic-retrospect.js +279 -0
  10. package/dist/critic-taxonomy.d.ts +40 -0
  11. package/dist/critic-taxonomy.js +146 -0
  12. package/dist/growth.d.ts +37 -0
  13. package/dist/growth.js +272 -0
  14. package/dist/integrations/ableton.d.ts +30 -0
  15. package/dist/integrations/ableton.js +66 -0
  16. package/dist/integrations/kbot-control-client.d.ts +66 -0
  17. package/dist/integrations/kbot-control-client.js +224 -0
  18. package/dist/observer.d.ts +13 -0
  19. package/dist/observer.js +5 -1
  20. package/dist/planner/hierarchical/dag.d.ts +71 -0
  21. package/dist/planner/hierarchical/dag.js +97 -0
  22. package/dist/planner/hierarchical/persistence.d.ts +26 -0
  23. package/dist/planner/hierarchical/persistence.js +113 -0
  24. package/dist/planner/hierarchical/session-planner.d.ts +68 -0
  25. package/dist/planner/hierarchical/session-planner.js +141 -0
  26. package/dist/planner/hierarchical/types.d.ts +116 -0
  27. package/dist/planner/hierarchical/types.js +18 -0
  28. package/dist/tool-pipeline.d.ts +39 -1
  29. package/dist/tool-pipeline.js +109 -1
  30. package/dist/tools/ableton-listen.d.ts +2 -0
  31. package/dist/tools/ableton-listen.js +126 -0
  32. package/dist/tools/ableton.js +477 -12
  33. package/dist/tools/index.js +2 -0
  34. package/dist/tools/kbot-control.d.ts +2 -0
  35. package/dist/tools/kbot-control.js +63 -0
  36. package/package.json +1 -1
package/dist/observer.js CHANGED
@@ -11,8 +11,12 @@
11
11
  // The log file is written by a Claude Code hook (PostToolUse) that appends
12
12
  // one JSON line per tool call to ~/.kbot/observer/session.jsonl
13
13
  //
14
- // Format per line:
14
+ // Format per line (schema v1 — legacy):
15
15
  // {"ts":"ISO","tool":"Read","args":{"file_path":"/src/foo.ts"},"result_length":1234,"session":"abc"}
16
+ //
17
+ // Format per line (schema v2 — includes action-token training fields):
18
+ // {"schema":2,"ts":"ISO","tool":"Read","args":{...},"result_length":1234,"session":"abc",
19
+ // "durationMs":42,"outcome":"success","resultSize":1234,"error":false}
16
20
  import { existsSync, readFileSync, writeFileSync, mkdirSync, appendFileSync } from 'node:fs';
17
21
  import { join } from 'node:path';
18
22
  import { homedir } from 'node:os';
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Task-Decoupled Planning (TDP) — DAG node types.
3
+ *
4
+ * Adopted from "Beyond Entangled Planning: Task-Decoupled Planning for
5
+ * Long-Horizon Agents" (arXiv:2601.07577, 2026). The paper reports up to 82%
6
+ * token reduction by decomposing a task into a DAG of sub-goals, each executed
7
+ * under a context scoped to only that node's ancestors — not the full history.
8
+ *
9
+ * This module is types-only. Phase 2 of the hierarchical planner will wrap
10
+ * existing Phase nodes as DAG nodes; no runtime here.
11
+ *
12
+ * Scoping rule (paper §3.2): a DAG node's context window contains
13
+ * - the node's own sub-goal + acceptance criteria
14
+ * - outputs (summaries) of ancestor nodes only
15
+ * - the typed tool schemas available for that node
16
+ * Non-ancestor siblings are excluded. Replanning fires only when the node's
17
+ * verifier rejects — never regenerates the whole trajectory.
18
+ */
19
+ import type { Phase, Action, TierVerdict } from './types.js';
20
+ /** Stable node identifier within a DAG. */
21
+ export type NodeId = string;
22
+ /**
23
+ * One node in the task DAG. In the kbot mapping, a `DAGNode` wraps a `Phase`
24
+ * and its child `Action`s, adding explicit parent edges and a scoped-context
25
+ * marker. Keeping `phase` as an embedded field means Phase 2 can migrate
26
+ * incrementally — tools that walk Phases still work.
27
+ */
28
+ export interface DAGNode {
29
+ id: NodeId;
30
+ /** Parent node ids — the node's inputs. Empty for root. */
31
+ parents: NodeId[];
32
+ /** The wrapped Phase. Its `id` is duplicated here as the node id. */
33
+ phase: Phase;
34
+ /** Actions executed inside this node. */
35
+ actions: Action[];
36
+ /**
37
+ * Summary of the node's output, produced once status flips to done.
38
+ * This is what descendants see — not the full action trace.
39
+ */
40
+ outputSummary?: string;
41
+ /** Verdicts emitted against this node only. */
42
+ verdicts: TierVerdict[];
43
+ status: 'pending' | 'running' | 'done' | 'failed';
44
+ }
45
+ export interface TaskDAG {
46
+ /** Root node ids — usually one. */
47
+ roots: NodeId[];
48
+ nodes: Record<NodeId, DAGNode>;
49
+ }
50
+ /**
51
+ * Build the context a node sees during execution. Paper §3.2 calls this the
52
+ * "scoped context". Returns ancestor summaries plus the node's own sub-goal.
53
+ *
54
+ * Ordering: breadth-first from roots, so earlier context appears first in the
55
+ * prompt. The ancestor set is closed under the transitive parent relation.
56
+ */
57
+ export declare function buildScopedContext(dag: TaskDAG, nodeId: NodeId): {
58
+ subGoal: string;
59
+ ancestorSummaries: Array<{
60
+ id: NodeId;
61
+ summary: string;
62
+ }>;
63
+ };
64
+ /**
65
+ * Topological order of `dag`. Throws on cycles — DAG invariant is caller's
66
+ * responsibility to maintain; this is the detector of last resort.
67
+ */
68
+ export declare function topologicalOrder(dag: TaskDAG): NodeId[];
69
+ /** Nodes whose parents are all done — eligible to run next. */
70
+ export declare function readyNodes(dag: TaskDAG): DAGNode[];
71
+ //# sourceMappingURL=dag.d.ts.map
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Task-Decoupled Planning (TDP) — DAG node types.
3
+ *
4
+ * Adopted from "Beyond Entangled Planning: Task-Decoupled Planning for
5
+ * Long-Horizon Agents" (arXiv:2601.07577, 2026). The paper reports up to 82%
6
+ * token reduction by decomposing a task into a DAG of sub-goals, each executed
7
+ * under a context scoped to only that node's ancestors — not the full history.
8
+ *
9
+ * This module is types-only. Phase 2 of the hierarchical planner will wrap
10
+ * existing Phase nodes as DAG nodes; no runtime here.
11
+ *
12
+ * Scoping rule (paper §3.2): a DAG node's context window contains
13
+ * - the node's own sub-goal + acceptance criteria
14
+ * - outputs (summaries) of ancestor nodes only
15
+ * - the typed tool schemas available for that node
16
+ * Non-ancestor siblings are excluded. Replanning fires only when the node's
17
+ * verifier rejects — never regenerates the whole trajectory.
18
+ */
19
+ /**
20
+ * Build the context a node sees during execution. Paper §3.2 calls this the
21
+ * "scoped context". Returns ancestor summaries plus the node's own sub-goal.
22
+ *
23
+ * Ordering: breadth-first from roots, so earlier context appears first in the
24
+ * prompt. The ancestor set is closed under the transitive parent relation.
25
+ */
26
+ export function buildScopedContext(dag, nodeId) {
27
+ const node = dag.nodes[nodeId];
28
+ if (!node) {
29
+ throw new Error(`buildScopedContext: node ${nodeId} not in DAG`);
30
+ }
31
+ const ancestors = collectAncestors(dag, nodeId);
32
+ const ancestorSummaries = [];
33
+ for (const aid of ancestors) {
34
+ const a = dag.nodes[aid];
35
+ if (a?.outputSummary) {
36
+ ancestorSummaries.push({ id: aid, summary: a.outputSummary });
37
+ }
38
+ }
39
+ return { subGoal: node.phase.objective, ancestorSummaries };
40
+ }
41
+ /** Transitive parents of `nodeId`, breadth-first, excluding the node itself. */
42
+ function collectAncestors(dag, nodeId) {
43
+ const seen = new Set();
44
+ const order = [];
45
+ const queue = [...(dag.nodes[nodeId]?.parents ?? [])];
46
+ while (queue.length > 0) {
47
+ const next = queue.shift();
48
+ if (seen.has(next))
49
+ continue;
50
+ seen.add(next);
51
+ order.push(next);
52
+ const parent = dag.nodes[next];
53
+ if (parent)
54
+ queue.push(...parent.parents);
55
+ }
56
+ return order;
57
+ }
58
+ /**
59
+ * Topological order of `dag`. Throws on cycles — DAG invariant is caller's
60
+ * responsibility to maintain; this is the detector of last resort.
61
+ */
62
+ export function topologicalOrder(dag) {
63
+ const inDegree = {};
64
+ for (const id of Object.keys(dag.nodes))
65
+ inDegree[id] = 0;
66
+ for (const node of Object.values(dag.nodes)) {
67
+ for (const _p of node.parents) {
68
+ inDegree[node.id] = (inDegree[node.id] ?? 0) + 1;
69
+ }
70
+ }
71
+ const ready = Object.keys(inDegree).filter(id => inDegree[id] === 0);
72
+ const out = [];
73
+ while (ready.length > 0) {
74
+ const id = ready.shift();
75
+ out.push(id);
76
+ for (const other of Object.values(dag.nodes)) {
77
+ if (other.parents.includes(id)) {
78
+ inDegree[other.id] -= 1;
79
+ if (inDegree[other.id] === 0)
80
+ ready.push(other.id);
81
+ }
82
+ }
83
+ }
84
+ if (out.length !== Object.keys(dag.nodes).length) {
85
+ throw new Error('topologicalOrder: cycle detected in task DAG');
86
+ }
87
+ return out;
88
+ }
89
+ /** Nodes whose parents are all done — eligible to run next. */
90
+ export function readyNodes(dag) {
91
+ return Object.values(dag.nodes).filter(n => {
92
+ if (n.status !== 'pending')
93
+ return false;
94
+ return n.parents.every(pid => dag.nodes[pid]?.status === 'done');
95
+ });
96
+ }
97
+ //# sourceMappingURL=dag.js.map
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Hierarchical Planner — persistence helpers.
3
+ *
4
+ * Goals are stored as individual JSON files under
5
+ * `~/.kbot/planner/goals/<id>.json`. The currently-active goal id is recorded
6
+ * at `~/.kbot/planner/active.json` as `{ "goalId": "<ulid>" }`.
7
+ *
8
+ * Scope: atomic read/write, listing, and active-pointer management. No
9
+ * tier logic here.
10
+ */
11
+ import type { SessionGoal } from './types.js';
12
+ /** Default on-disk root: `~/.kbot/planner/`. */
13
+ export declare function defaultStateDir(): string;
14
+ /** Read a single goal by id, or null if missing. */
15
+ export declare function readGoal(stateDir: string, id: string): Promise<SessionGoal | null>;
16
+ /** Write (create-or-overwrite) a goal file. */
17
+ export declare function writeGoal(stateDir: string, goal: SessionGoal): Promise<void>;
18
+ /** List every goal on disk (unsorted). */
19
+ export declare function listGoals(stateDir: string): Promise<SessionGoal[]>;
20
+ /** Set the active goal pointer. The goal must already exist on disk. */
21
+ export declare function setActive(stateDir: string, goalId: string): Promise<void>;
22
+ /** Read the active goal (resolves pointer → goal file). Returns null if unset. */
23
+ export declare function getActive(stateDir: string): Promise<SessionGoal | null>;
24
+ /** Clear the active pointer (goal files untouched). */
25
+ export declare function clearActive(stateDir: string): Promise<void>;
26
+ //# sourceMappingURL=persistence.d.ts.map
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Hierarchical Planner — persistence helpers.
3
+ *
4
+ * Goals are stored as individual JSON files under
5
+ * `~/.kbot/planner/goals/<id>.json`. The currently-active goal id is recorded
6
+ * at `~/.kbot/planner/active.json` as `{ "goalId": "<ulid>" }`.
7
+ *
8
+ * Scope: atomic read/write, listing, and active-pointer management. No
9
+ * tier logic here.
10
+ */
11
+ import { promises as fs } from 'node:fs';
12
+ import * as os from 'node:os';
13
+ import * as path from 'node:path';
14
+ /** Default on-disk root: `~/.kbot/planner/`. */
15
+ export function defaultStateDir() {
16
+ return path.join(os.homedir(), '.kbot', 'planner');
17
+ }
18
+ function goalsDir(stateDir) {
19
+ return path.join(stateDir, 'goals');
20
+ }
21
+ function goalPath(stateDir, id) {
22
+ return path.join(goalsDir(stateDir), `${id}.json`);
23
+ }
24
+ function activePath(stateDir) {
25
+ return path.join(stateDir, 'active.json');
26
+ }
27
+ async function ensureDir(dir) {
28
+ await fs.mkdir(dir, { recursive: true });
29
+ }
30
+ /** Read a single goal by id, or null if missing. */
31
+ export async function readGoal(stateDir, id) {
32
+ try {
33
+ const raw = await fs.readFile(goalPath(stateDir, id), 'utf8');
34
+ return JSON.parse(raw);
35
+ }
36
+ catch (err) {
37
+ if (err.code === 'ENOENT')
38
+ return null;
39
+ throw err;
40
+ }
41
+ }
42
+ /** Write (create-or-overwrite) a goal file. */
43
+ export async function writeGoal(stateDir, goal) {
44
+ await ensureDir(goalsDir(stateDir));
45
+ const tmp = goalPath(stateDir, goal.id) + '.tmp';
46
+ const final = goalPath(stateDir, goal.id);
47
+ await fs.writeFile(tmp, JSON.stringify(goal, null, 2), 'utf8');
48
+ await fs.rename(tmp, final);
49
+ }
50
+ /** List every goal on disk (unsorted). */
51
+ export async function listGoals(stateDir) {
52
+ const dir = goalsDir(stateDir);
53
+ let entries;
54
+ try {
55
+ entries = await fs.readdir(dir);
56
+ }
57
+ catch (err) {
58
+ if (err.code === 'ENOENT')
59
+ return [];
60
+ throw err;
61
+ }
62
+ const goals = [];
63
+ for (const entry of entries) {
64
+ if (!entry.endsWith('.json'))
65
+ continue;
66
+ try {
67
+ const raw = await fs.readFile(path.join(dir, entry), 'utf8');
68
+ goals.push(JSON.parse(raw));
69
+ }
70
+ catch {
71
+ // skip unreadable / malformed files; don't let one bad file poison the list
72
+ }
73
+ }
74
+ return goals;
75
+ }
76
+ /** Set the active goal pointer. The goal must already exist on disk. */
77
+ export async function setActive(stateDir, goalId) {
78
+ const existing = await readGoal(stateDir, goalId);
79
+ if (!existing) {
80
+ throw new Error(`setActive: goal ${goalId} not found in ${goalsDir(stateDir)}`);
81
+ }
82
+ await ensureDir(stateDir);
83
+ const tmp = activePath(stateDir) + '.tmp';
84
+ await fs.writeFile(tmp, JSON.stringify({ goalId }, null, 2), 'utf8');
85
+ await fs.rename(tmp, activePath(stateDir));
86
+ }
87
+ /** Read the active goal (resolves pointer → goal file). Returns null if unset. */
88
+ export async function getActive(stateDir) {
89
+ let ptr;
90
+ try {
91
+ const raw = await fs.readFile(activePath(stateDir), 'utf8');
92
+ ptr = JSON.parse(raw);
93
+ }
94
+ catch (err) {
95
+ if (err.code === 'ENOENT')
96
+ return null;
97
+ throw err;
98
+ }
99
+ if (!ptr.goalId)
100
+ return null;
101
+ return readGoal(stateDir, ptr.goalId);
102
+ }
103
+ /** Clear the active pointer (goal files untouched). */
104
+ export async function clearActive(stateDir) {
105
+ try {
106
+ await fs.unlink(activePath(stateDir));
107
+ }
108
+ catch (err) {
109
+ if (err.code !== 'ENOENT')
110
+ throw err;
111
+ }
112
+ }
113
+ //# sourceMappingURL=persistence.js.map
@@ -0,0 +1,68 @@
1
+ /**
2
+ * HierarchicalPlanner — Phase 1 passthrough.
3
+ *
4
+ * This file lays the pipe for the four-tier hierarchical planner described in
5
+ * ./types.ts. Today it does NOT implement tiers — `planAndExecute` delegates
6
+ * the entire user turn to the existing flat `autonomousExecute` from
7
+ * ../../planner.ts and synthesizes a `PlannerResult` shell around it.
8
+ *
9
+ * Real goal persistence is live: `loadGoal`, `createGoal`, and `setGoal` read
10
+ * and write `~/.kbot/planner/goals/<id>.json` plus the `active.json` pointer.
11
+ * The current PlannerResult.goal is always `null` in this phase because the
12
+ * tier-1 selection/creation loop isn't wired in yet — callers opt into goal
13
+ * tracking explicitly via createGoal/setGoal.
14
+ *
15
+ * Feature flag: `planAndExecute` throws unless `process.env.KBOT_PLANNER ===
16
+ * 'hierarchical'`. Nothing in production calls this class yet.
17
+ */
18
+ import type { AgentOptions } from '../../agent.js';
19
+ import type { Action, Phase, SessionGoal, TurnMetrics } from './types.js';
20
+ export type Ulid = string;
21
+ /** Minimal shape planAndExecute needs from the caller. Expand in Phase 2. */
22
+ export interface SessionContext {
23
+ /** Optional session identifier (e.g. from memory.ts). */
24
+ sessionId?: string;
25
+ /** Agent options to thread into the underlying planner. */
26
+ agentOpts: AgentOptions;
27
+ /** When true, skip interactive approval on the underlying planner. */
28
+ autoApprove?: boolean;
29
+ }
30
+ /**
31
+ * Phase-1 `PlannerResult`. Shape matches the spec in this ticket — NOT the
32
+ * richer `PlannerResult` in ./types.ts, which targets Phase 2+. We use a local
33
+ * name to avoid a clash and to flag this is transitional.
34
+ */
35
+ export interface PlannerResult {
36
+ /** Always null in Phase 1; Phase 2 will populate via tier-1 logic. */
37
+ goal: SessionGoal | null;
38
+ /** Ephemeral placeholder phase spanning just this turn. */
39
+ phase: Phase;
40
+ /** The action derived from autonomousExecute's flat plan. */
41
+ action: Action;
42
+ metrics: TurnMetrics;
43
+ }
44
+ export interface HierarchicalPlannerOptions {
45
+ /** Override persistence root; defaults to `~/.kbot/planner/`. */
46
+ stateDir?: string;
47
+ }
48
+ export declare class HierarchicalPlanner {
49
+ private readonly stateDir;
50
+ constructor(opts?: HierarchicalPlannerOptions);
51
+ /**
52
+ * Plan and execute one user turn.
53
+ *
54
+ * Phase 1: feature-flag gated passthrough to autonomousExecute.
55
+ * Phase 2+: replace the body with the Tier 1–4 cascade.
56
+ */
57
+ planAndExecute(userTurn: string, ctx: SessionContext): Promise<PlannerResult>;
58
+ /** Read the currently-active goal from disk, or null if none is set. */
59
+ loadGoal(): Promise<SessionGoal | null>;
60
+ /**
61
+ * Create a new goal on disk and mark it active.
62
+ * Fields the caller omits are filled with sensible defaults.
63
+ */
64
+ createGoal(spec?: Partial<SessionGoal>): Promise<SessionGoal>;
65
+ /** Activate an existing goal by id. Throws if the goal does not exist. */
66
+ setGoal(goalId: Ulid): Promise<void>;
67
+ }
68
+ //# sourceMappingURL=session-planner.d.ts.map
@@ -0,0 +1,141 @@
1
+ /**
2
+ * HierarchicalPlanner — Phase 1 passthrough.
3
+ *
4
+ * This file lays the pipe for the four-tier hierarchical planner described in
5
+ * ./types.ts. Today it does NOT implement tiers — `planAndExecute` delegates
6
+ * the entire user turn to the existing flat `autonomousExecute` from
7
+ * ../../planner.ts and synthesizes a `PlannerResult` shell around it.
8
+ *
9
+ * Real goal persistence is live: `loadGoal`, `createGoal`, and `setGoal` read
10
+ * and write `~/.kbot/planner/goals/<id>.json` plus the `active.json` pointer.
11
+ * The current PlannerResult.goal is always `null` in this phase because the
12
+ * tier-1 selection/creation loop isn't wired in yet — callers opt into goal
13
+ * tracking explicitly via createGoal/setGoal.
14
+ *
15
+ * Feature flag: `planAndExecute` throws unless `process.env.KBOT_PLANNER ===
16
+ * 'hierarchical'`. Nothing in production calls this class yet.
17
+ */
18
+ import { randomBytes } from 'node:crypto';
19
+ import { autonomousExecute } from '../../planner.js';
20
+ import { defaultStateDir, getActive, readGoal, setActive, writeGoal, } from './persistence.js';
21
+ // ─────────────────────────────────────────────────────────────────────────────
22
+ // ULID-ish id generator.
23
+ // We depend on `ulid` if it's resolvable at runtime, otherwise fall back to a
24
+ // 26-char Crockford-base32 string sourced from crypto.randomBytes. Either way
25
+ // IDs are sortable-ish and collision-resistant enough for per-goal filenames.
26
+ // ─────────────────────────────────────────────────────────────────────────────
27
+ const CROCKFORD = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
28
+ function cryptoUlid() {
29
+ // 10 chars of timestamp (ms) + 16 chars of randomness = 26 chars.
30
+ let ts = Date.now();
31
+ const tsChars = [];
32
+ for (let i = 0; i < 10; i++) {
33
+ tsChars.unshift(CROCKFORD[ts % 32]);
34
+ ts = Math.floor(ts / 32);
35
+ }
36
+ const bytes = randomBytes(10);
37
+ const rand = [];
38
+ // 80 bits → 16 base32 chars. Pull 5 bits at a time.
39
+ let acc = 0;
40
+ let accBits = 0;
41
+ for (const b of bytes) {
42
+ acc = (acc << 8) | b;
43
+ accBits += 8;
44
+ while (accBits >= 5) {
45
+ accBits -= 5;
46
+ rand.push(CROCKFORD[(acc >> accBits) & 0x1f]);
47
+ }
48
+ }
49
+ return tsChars.join('') + rand.slice(0, 16).join('');
50
+ }
51
+ export class HierarchicalPlanner {
52
+ stateDir;
53
+ constructor(opts = {}) {
54
+ this.stateDir = opts.stateDir ?? defaultStateDir();
55
+ }
56
+ /**
57
+ * Plan and execute one user turn.
58
+ *
59
+ * Phase 1: feature-flag gated passthrough to autonomousExecute.
60
+ * Phase 2+: replace the body with the Tier 1–4 cascade.
61
+ */
62
+ async planAndExecute(userTurn, ctx) {
63
+ const flag = process.env.KBOT_PLANNER;
64
+ if (flag !== 'hierarchical') {
65
+ throw new Error(`HierarchicalPlanner.planAndExecute is gated behind KBOT_PLANNER=hierarchical ` +
66
+ `(got ${flag ?? 'unset'}). This path is Phase-1 scaffolding only.`);
67
+ }
68
+ const startedAt = Date.now();
69
+ const plan = await autonomousExecute(userTurn, ctx.agentOpts, {
70
+ autoApprove: ctx.autoApprove ?? true,
71
+ });
72
+ const phase = {
73
+ id: cryptoUlid(),
74
+ goalId: '', // no goal linked in Phase 1
75
+ kind: 'other',
76
+ objective: userTurn,
77
+ exitCriteria: [],
78
+ startedAt: new Date(startedAt).toISOString(),
79
+ endedAt: new Date().toISOString(),
80
+ status: plan.status === 'completed' ? 'done' : 'active',
81
+ };
82
+ const steps = plan.steps.map(step => ({ ...step }));
83
+ const action = {
84
+ id: cryptoUlid(),
85
+ phaseId: phase.id,
86
+ userTurn,
87
+ summary: plan.summary,
88
+ steps,
89
+ createdAt: plan.createdAt,
90
+ status: plan.status === 'completed'
91
+ ? 'done'
92
+ : plan.status === 'failed'
93
+ ? 'failed'
94
+ : 'running',
95
+ };
96
+ const metrics = {
97
+ tier1Calls: 0,
98
+ tier2Calls: 0,
99
+ tier3Calls: 1, // autonomousExecute counts as one tier-3 invocation
100
+ tier4Calls: steps.length,
101
+ tokensIn: 0,
102
+ tokensOut: 0,
103
+ wallMs: Date.now() - startedAt,
104
+ };
105
+ // Phase 1 never attaches a goal — that's Phase 2.
106
+ return { goal: null, phase, action, metrics };
107
+ }
108
+ /** Read the currently-active goal from disk, or null if none is set. */
109
+ async loadGoal() {
110
+ return getActive(this.stateDir);
111
+ }
112
+ /**
113
+ * Create a new goal on disk and mark it active.
114
+ * Fields the caller omits are filled with sensible defaults.
115
+ */
116
+ async createGoal(spec = {}) {
117
+ const now = new Date().toISOString();
118
+ const goal = {
119
+ id: spec.id ?? cryptoUlid(),
120
+ title: spec.title ?? '(untitled goal)',
121
+ intent: spec.intent ?? '',
122
+ acceptance: spec.acceptance ?? [],
123
+ createdAt: spec.createdAt ?? now,
124
+ updatedAt: spec.updatedAt ?? now,
125
+ status: spec.status ?? 'active',
126
+ tags: spec.tags,
127
+ };
128
+ await writeGoal(this.stateDir, goal);
129
+ await setActive(this.stateDir, goal.id);
130
+ return goal;
131
+ }
132
+ /** Activate an existing goal by id. Throws if the goal does not exist. */
133
+ async setGoal(goalId) {
134
+ const existing = await readGoal(this.stateDir, goalId);
135
+ if (!existing) {
136
+ throw new Error(`setGoal: goal ${goalId} not found under ${this.stateDir}`);
137
+ }
138
+ await setActive(this.stateDir, goalId);
139
+ }
140
+ }
141
+ //# sourceMappingURL=session-planner.js.map
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Hierarchical Planner — Type Definitions
3
+ *
4
+ * Four tiers, increasing temporal resolution:
5
+ * Tier 1 SessionGoal days → weeks (Opus, rarely re-planned)
6
+ * Tier 2 Phase hours (Opus, on scope shift)
7
+ * Tier 3 Action minutes (Sonnet, per user turn)
8
+ * Tier 4 ToolCallSpec seconds (Haiku, per tool call)
9
+ *
10
+ * Inspired by Suno's 3-stage transformer (semantic → coarse acoustic → fine
11
+ * acoustic). Coarse intent is stable; fine actuation is cheap and rewritten.
12
+ *
13
+ * See DESIGN.md in this directory for the full rationale, cost model,
14
+ * decision logic, and integration plan. This file is types-only — no imports
15
+ * from heavy runtime modules, no implementation.
16
+ */
17
+ import type { PlanStep } from '../../planner.js';
18
+ /** Long-lived user objective that spans many turns and possibly many sessions. */
19
+ export interface SessionGoal {
20
+ /** Stable identifier (uuid). Persists across sessions. */
21
+ id: string;
22
+ /** Short title: "ship hierarchical planner v1". */
23
+ title: string;
24
+ /** 1–3 sentence rationale: what success looks like. */
25
+ intent: string;
26
+ /** Acceptance criteria — bullet list the user would agree closes the goal. */
27
+ acceptance: string[];
28
+ /** ISO timestamps. */
29
+ createdAt: string;
30
+ updatedAt: string;
31
+ /** Lifecycle. `paused` goals stay on disk but don't get re-planned. */
32
+ status: 'active' | 'paused' | 'completed' | 'abandoned';
33
+ /** Free-form tags: repo name, domain, user-supplied label. */
34
+ tags?: string[];
35
+ }
36
+ /** Coarse mode the agent is currently operating in. */
37
+ export type PhaseKind = 'explore' | 'build' | 'debug' | 'review' | 'write' | 'refactor' | 'deploy' | 'other';
38
+ /** A contiguous stretch of work under one mode, toward one milestone. */
39
+ export interface Phase {
40
+ id: string;
41
+ /** Parent goal. */
42
+ goalId: string;
43
+ kind: PhaseKind;
44
+ /** What this phase commits to producing. */
45
+ objective: string;
46
+ /** Exit criteria — when `kind` should flip or phase should close. */
47
+ exitCriteria: string[];
48
+ /** Files or subsystems scoped in. */
49
+ scope?: string[];
50
+ /** ISO timestamps. */
51
+ startedAt: string;
52
+ endedAt?: string;
53
+ status: 'active' | 'done' | 'aborted';
54
+ }
55
+ /**
56
+ * One action = one user turn's plan. Steps are the existing `PlanStep`s so we
57
+ * stay compatible with `planner.ts#executePlan`.
58
+ */
59
+ export interface ActionStep extends PlanStep {
60
+ }
61
+ export interface Action {
62
+ id: string;
63
+ phaseId: string;
64
+ /** Verbatim user turn that triggered this action. */
65
+ userTurn: string;
66
+ /** One-sentence plan. */
67
+ summary: string;
68
+ /** Ordered steps; each step is a PlanStep-compatible record. */
69
+ steps: ActionStep[];
70
+ /** Agents this action expects to consult (from learned-router). */
71
+ expectedAgents?: string[];
72
+ createdAt: string;
73
+ status: 'pending' | 'running' | 'done' | 'failed';
74
+ }
75
+ /** Coarse hazard class used to gate permissions and verdict logic. */
76
+ export type SideEffectClass = 'pure' | 'read' | 'write' | 'exec' | 'network' | 'destructive' | 'external';
77
+ /** The final low-level tool call. Haiku fills this in. */
78
+ export interface ToolCallSpec {
79
+ id: string;
80
+ actionId: string;
81
+ stepId: number;
82
+ tool: string;
83
+ args: Record<string, unknown>;
84
+ sideEffect: SideEffectClass;
85
+ /** Optional prediction of what the tool should return on success. */
86
+ expectedOutcome?: string;
87
+ /** Hard ceiling in ms; falls back to pipeline default when absent. */
88
+ timeoutMs?: number;
89
+ }
90
+ export type VerdictDecision = 'continue' | 'revise-action' | 'revise-phase' | 'revise-goal' | 'abort';
91
+ /** Emitted after every tool call; consumed by the up-delegation ladder. */
92
+ export interface TierVerdict {
93
+ decision: VerdictDecision;
94
+ tier: 'tool' | 'action' | 'phase' | 'goal';
95
+ reason: string;
96
+ /** Evidence: tool error, failed assertion, diff summary. */
97
+ evidence?: string;
98
+ }
99
+ export interface TurnMetrics {
100
+ tier1Calls: number;
101
+ tier2Calls: number;
102
+ tier3Calls: number;
103
+ tier4Calls: number;
104
+ tokensIn: number;
105
+ tokensOut: number;
106
+ wallMs: number;
107
+ }
108
+ /** Top-level return of `HierarchicalPlanner.planTurn`. */
109
+ export interface PlannerResult {
110
+ goal: SessionGoal;
111
+ phase: Phase;
112
+ action: Action;
113
+ verdicts: TierVerdict[];
114
+ metrics: TurnMetrics;
115
+ }
116
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Hierarchical Planner — Type Definitions
3
+ *
4
+ * Four tiers, increasing temporal resolution:
5
+ * Tier 1 SessionGoal days → weeks (Opus, rarely re-planned)
6
+ * Tier 2 Phase hours (Opus, on scope shift)
7
+ * Tier 3 Action minutes (Sonnet, per user turn)
8
+ * Tier 4 ToolCallSpec seconds (Haiku, per tool call)
9
+ *
10
+ * Inspired by Suno's 3-stage transformer (semantic → coarse acoustic → fine
11
+ * acoustic). Coarse intent is stable; fine actuation is cheap and rewritten.
12
+ *
13
+ * See DESIGN.md in this directory for the full rationale, cost model,
14
+ * decision logic, and integration plan. This file is types-only — no imports
15
+ * from heavy runtime modules, no implementation.
16
+ */
17
+ export {};
18
+ //# sourceMappingURL=types.js.map