@cleocode/goal 0.0.0-stage → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +319 -0
- package/LICENSE +21 -0
- package/README.md +141 -2
- package/cli/ocg.ts +149 -0
- package/dist/cli.js +8708 -0
- package/dist/tui.d.ts +2 -0
- package/dist/tui.js +1920 -0
- package/package.json +79 -4
- package/rpc.ts +2 -0
- package/server.ts +3 -0
- package/skills/write-goal/SKILL.md +211 -0
- package/skills/write-goal/assets/goal.template.yaml +38 -0
- package/skills/write-goal/references/examples.md +283 -0
- package/skills/write-goal/references/goal-schema.md +53 -0
- package/skills/write-goal/references/interview.md +36 -0
- package/src/cli/install.ts +125 -0
- package/src/cli/status.ts +116 -0
- package/src/contract/parse.ts +454 -0
- package/src/contract/render.ts +134 -0
- package/src/contract/types.ts +158 -0
- package/src/engine/amendment.ts +51 -0
- package/src/engine/amendments.ts +72 -0
- package/src/engine/cleo-bridge.ts +339 -0
- package/src/engine/cleo-link.ts +150 -0
- package/src/engine/continue-work.ts +131 -0
- package/src/engine/decisions.ts +44 -0
- package/src/engine/dogfood-record.ts +278 -0
- package/src/engine/handoff.ts +102 -0
- package/src/engine/interrupt-policy.ts +45 -0
- package/src/engine/inventory.ts +70 -0
- package/src/engine/launches.ts +82 -0
- package/src/engine/loop-stop.ts +50 -0
- package/src/engine/loop.ts +109 -0
- package/src/engine/next-work.ts +82 -0
- package/src/engine/owner-intent.ts +71 -0
- package/src/engine/owner-reviews.ts +178 -0
- package/src/engine/peer-announce.ts +67 -0
- package/src/engine/peer-transport.ts +78 -0
- package/src/engine/policy.ts +42 -0
- package/src/engine/registry.ts +119 -0
- package/src/engine/similarity.ts +65 -0
- package/src/engine/stale-decisions.ts +68 -0
- package/src/engine/state.ts +185 -0
- package/src/engine/store.ts +263 -0
- package/src/engine/tick-record.ts +129 -0
- package/src/engine/tick.ts +207 -0
- package/src/rpc.ts +182 -0
- package/src/server/app.ts +4320 -0
- package/src/server/help.ts +50 -0
- package/src/server/index.ts +23 -0
- package/src/server/intent.ts +22 -0
- package/src/server/messages.ts +228 -0
- package/src/summary/audit.ts +76 -0
- package/src/summary/build.ts +314 -0
- package/src/tui/dashboard.ts +322 -0
- package/src/tui/decision-form.ts +12 -0
- package/src/tui/format.ts +127 -0
- package/src/tui/index.tsx +718 -0
- package/src/tui/manager.ts +94 -0
- package/src/tui/navigation.ts +11 -0
- package/src/tui/pointer-select.ts +20 -0
- package/src/tui/tracer.ts +51 -0
- package/src/util/git.ts +102 -0
- package/src/util/ids.ts +20 -0
- package/src/util/shell.ts +42 -0
- package/src/verify/artifact.ts +95 -0
- package/src/verify/pipeline.ts +200 -0
- package/tui.tsx +2 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// v0.7.0 - the `ocg tick` entry point: RECORD the turn, DECIDE, then act.
|
|
2
|
+
//
|
|
3
|
+
// CLEO's Stop dispatcher spawns `ocg tick` with the harness's Stop JSON on stdin
|
|
4
|
+
// and reads JSON ONLY from stdout:
|
|
5
|
+
// {"decision":"block","reason":"<continuation>"} keep the agent working
|
|
6
|
+
// {"final":false} hold the session open
|
|
7
|
+
// anything else (including non-JSON, or nothing) no live goal
|
|
8
|
+
// Logs go to stderr. It runs only where `.opencode/goals/` exists.
|
|
9
|
+
//
|
|
10
|
+
// v0.6.0 made the tick decide; it was read-only, so nothing advanced the counters
|
|
11
|
+
// the stall, recovery, wrap-up and claim decisions read - a goal could never be
|
|
12
|
+
// driven to an end outside OpenCode. v0.7.0 records the turn first (through OCG's
|
|
13
|
+
// own store, in the adapter's own shape) and RUNS the verification of a claim, so
|
|
14
|
+
// a host-only goal can actually reach `complete`. Everything it records is what a
|
|
15
|
+
// Stop-time observer can know or recover from the payload - never a guess, and a
|
|
16
|
+
// missing payload, transcript or store is a stated degradation, not an error.
|
|
17
|
+
|
|
18
|
+
import { existsSync } from "node:fs"
|
|
19
|
+
import { join } from "node:path"
|
|
20
|
+
import type { Contract } from "../contract/types"
|
|
21
|
+
import { renderTailNote } from "../contract/render"
|
|
22
|
+
import { budgetUse, setStatus, TERMINAL as TERMINAL_STATUSES, type RunState } from "./state"
|
|
23
|
+
import { continuationTail, decide, type LoopLimits, type LoopSignals } from "./loop"
|
|
24
|
+
import { recordTurn } from "./tick-record"
|
|
25
|
+
import type { VerifyOutcome } from "../verify/pipeline"
|
|
26
|
+
|
|
27
|
+
/** The plugin's own defaults, so a tick agrees with the adapter unless overridden. */
|
|
28
|
+
export const TICK_LIMITS: LoopLimits = { blockerRepeats: 3, stallTurns: 3, recoveryAt: 2, backstopTurns: 200 }
|
|
29
|
+
export const TICK_COOLDOWN_MS = 1_500
|
|
30
|
+
/** CLEO caps a forwarded reason at 4000 characters. */
|
|
31
|
+
export const REASON_CAP = 4_000
|
|
32
|
+
|
|
33
|
+
/** The store surface the tick writes through - OCG's own store, never a hand edit. */
|
|
34
|
+
export type TickStore = {
|
|
35
|
+
slugs: () => string[]
|
|
36
|
+
readRun: (slug: string) => RunState | undefined
|
|
37
|
+
writeRun: (state: RunState) => void
|
|
38
|
+
readContract: (slug: string) => { contract?: Contract; text: string } | undefined
|
|
39
|
+
ledger: (slug: string, event: Record<string, unknown> & { type: string }) => void
|
|
40
|
+
evidence: (slug: string, runId: string, name: string, data: unknown) => string
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type TickDeps = {
|
|
44
|
+
root: string
|
|
45
|
+
store: TickStore
|
|
46
|
+
/** The repository fingerprint now (git), or undefined when it cannot be taken. */
|
|
47
|
+
fingerprint?: () => string | undefined
|
|
48
|
+
/** Run the claim verification. Absent means the tick cannot finish a goal. */
|
|
49
|
+
verify?: (state: RunState, contract: Contract, contractText: string) => Promise<VerifyOutcome>
|
|
50
|
+
/** The raw harness Stop payload, when the harness passed one. */
|
|
51
|
+
payload?: string
|
|
52
|
+
readTranscript?: (path: string) => string | undefined
|
|
53
|
+
limits?: LoopLimits
|
|
54
|
+
cooldownMs?: number
|
|
55
|
+
now?: number
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export type TickResult = {
|
|
59
|
+
/** Exactly what goes to stdout (empty when no goal is live). */
|
|
60
|
+
stdout: string
|
|
61
|
+
/** Lines for stderr. */
|
|
62
|
+
log: string[]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const BLOCK = (reason: string) => JSON.stringify({ decision: "block", reason })
|
|
66
|
+
const HOLD = JSON.stringify({ final: false })
|
|
67
|
+
const why = (error: unknown): string => (error instanceof Error ? error.message : String(error))
|
|
68
|
+
|
|
69
|
+
/** The newest non-terminal run in the project, by `updatedAt`.
|
|
70
|
+
*
|
|
71
|
+
* An absent or unreadable store is a stated degradation, never an exception: a
|
|
72
|
+
* crash looks exactly like "no live goal" to the dispatcher (empty stdout), which
|
|
73
|
+
* would silently drop the run. If a record cannot be read we cannot rule out a
|
|
74
|
+
* live goal, so the caller HOLDS rather than printing nothing. */
|
|
75
|
+
function liveRun(store: TickStore, log: string[]): { live?: RunState; unreadable: boolean } {
|
|
76
|
+
let slugs: string[]
|
|
77
|
+
try {
|
|
78
|
+
slugs = store.slugs()
|
|
79
|
+
} catch (error) {
|
|
80
|
+
log.push(`could not read the run store (${why(error)}); cannot rule out a live goal`)
|
|
81
|
+
return { unreadable: true }
|
|
82
|
+
}
|
|
83
|
+
let live: RunState | undefined
|
|
84
|
+
let unreadable = false
|
|
85
|
+
for (const slug of slugs) {
|
|
86
|
+
let state: RunState | undefined
|
|
87
|
+
try {
|
|
88
|
+
state = store.readRun(slug)
|
|
89
|
+
} catch (error) {
|
|
90
|
+
log.push(`could not read the run record for ${slug} (${why(error)}); cannot rule it out`)
|
|
91
|
+
unreadable = true
|
|
92
|
+
continue
|
|
93
|
+
}
|
|
94
|
+
if (!state?.slug || TERMINAL_STATUSES.includes(state.status)) continue
|
|
95
|
+
if (!live || (state.updatedAt ?? 0) > (live.updatedAt ?? 0)) live = state
|
|
96
|
+
}
|
|
97
|
+
return { live, unreadable }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function tick(deps: TickDeps): Promise<TickResult> {
|
|
101
|
+
const log: string[] = []
|
|
102
|
+
const now = deps.now ?? Date.now()
|
|
103
|
+
const limits = deps.limits ?? TICK_LIMITS
|
|
104
|
+
// Outside a goal project there is nothing to tick at all - print nothing.
|
|
105
|
+
if (!existsSync(join(deps.root, ".opencode", "goals"))) {
|
|
106
|
+
log.push("no .opencode/goals directory; nothing to tick")
|
|
107
|
+
return { stdout: "", log }
|
|
108
|
+
}
|
|
109
|
+
const { live, unreadable } = liveRun(deps.store, log)
|
|
110
|
+
if (!live && unreadable) {
|
|
111
|
+
log.push("a run record could not be read; cannot rule out a live goal - holding")
|
|
112
|
+
return { stdout: HOLD, log }
|
|
113
|
+
}
|
|
114
|
+
if (!live) {
|
|
115
|
+
log.push("no live goal here; nothing to tick")
|
|
116
|
+
return { stdout: "", log }
|
|
117
|
+
}
|
|
118
|
+
let read: { contract?: Contract; text: string } | undefined
|
|
119
|
+
try {
|
|
120
|
+
read = deps.store.readContract(live.slug)
|
|
121
|
+
} catch (error) {
|
|
122
|
+
log.push(`could not read the contract for ${live.slug} (${why(error)}); holding`)
|
|
123
|
+
return { stdout: HOLD, log }
|
|
124
|
+
}
|
|
125
|
+
if (!read?.contract) {
|
|
126
|
+
log.push(`goal ${live.slug} has no readable contract; holding`)
|
|
127
|
+
return { stdout: HOLD, log }
|
|
128
|
+
}
|
|
129
|
+
const contract = read.contract
|
|
130
|
+
|
|
131
|
+
// ── 1. RECORD the turn, through OCG's own store, in the adapter's own shape.
|
|
132
|
+
const record = recordTurn(live, contract, { fingerprint: deps.fingerprint ?? (() => undefined), ...(deps.payload ? { payload: deps.payload } : {}), ...(deps.readTranscript ? { readTranscript: deps.readTranscript } : {}), now })
|
|
133
|
+
deps.store.ledger(live.slug, {
|
|
134
|
+
type: "turn",
|
|
135
|
+
turn: live.turn,
|
|
136
|
+
kind: "tick",
|
|
137
|
+
tools: live.turnFacts.tools,
|
|
138
|
+
activity: live.turnFacts.tools > 0,
|
|
139
|
+
changed: record.changed,
|
|
140
|
+
progress: record.progress,
|
|
141
|
+
noProgress: live.counters.noProgress,
|
|
142
|
+
})
|
|
143
|
+
deps.store.writeRun(live)
|
|
144
|
+
log.push(`recorded turn ${record.turn} for ${live.slug} (${record.progress ? "progress" : "no progress"}${record.claimHonoured ? ", claim honoured" : ""})`)
|
|
145
|
+
for (const gap of record.couldNotSee) log.push(`could not see ${gap}`)
|
|
146
|
+
|
|
147
|
+
// ── 2. DECIDE, with the same pure function the OpenCode adapter uses.
|
|
148
|
+
const signals: LoopSignals = {
|
|
149
|
+
turn: "succeeded",
|
|
150
|
+
...(live.pending ? { pending: live.pending.kind } : {}),
|
|
151
|
+
changed: record.changed,
|
|
152
|
+
progressRecorded: record.progress,
|
|
153
|
+
stepsChanged: false,
|
|
154
|
+
activeStalled: false,
|
|
155
|
+
stallReason: `stalled: no change the host could see for ${live.counters.noProgress} turns`,
|
|
156
|
+
onStall: contract.autonomy.on_stall,
|
|
157
|
+
budgetRatio: budgetUse(live, contract).ratio,
|
|
158
|
+
backstop: false,
|
|
159
|
+
cooldownMs: deps.cooldownMs ?? TICK_COOLDOWN_MS,
|
|
160
|
+
now,
|
|
161
|
+
}
|
|
162
|
+
const action = decide(live, signals, limits)
|
|
163
|
+
|
|
164
|
+
// ── 3. ACT.
|
|
165
|
+
switch (action.kind) {
|
|
166
|
+
case "verify": {
|
|
167
|
+
if (!deps.verify) {
|
|
168
|
+
log.push("a claim is pending but this tick cannot verify; holding")
|
|
169
|
+
return { stdout: HOLD, log }
|
|
170
|
+
}
|
|
171
|
+
live.claim = undefined
|
|
172
|
+
setStatus(live, "verifying", "tick verification")
|
|
173
|
+
deps.store.ledger(live.slug, { type: "verify-start", source: "claim", turn: live.turn })
|
|
174
|
+
deps.store.writeRun(live)
|
|
175
|
+
const outcome = await deps.verify(live, contract, read.text)
|
|
176
|
+
for (const r of outcome.results) live.criteria[r.id] = { status: r.pass ? "pass" : "fail", by: r.by, at: now, detail: r.detail.slice(0, 600), rejections: live.criteria[r.id]?.rejections ?? 0 }
|
|
177
|
+
live.verdict = { passed: outcome.passed, lines: outcome.lines.slice(0, 20), at: now, turn: live.turn }
|
|
178
|
+
deps.store.evidence(live.slug, live.runId, `verify-tick-${live.turn}`, outcome)
|
|
179
|
+
if (outcome.passed) {
|
|
180
|
+
setStatus(live, "complete", "all required criteria verified")
|
|
181
|
+
deps.store.ledger(live.slug, { type: "complete", turn: live.turn })
|
|
182
|
+
deps.store.writeRun(live)
|
|
183
|
+
log.push(`goal ${live.slug} complete: ${outcome.lines[0] ?? "all required criteria verified"}`)
|
|
184
|
+
return { stdout: "", log } // no live goal remains; the harness session may end
|
|
185
|
+
}
|
|
186
|
+
// Not proven: keep the run going and hand the agent the verdict.
|
|
187
|
+
setStatus(live, "running")
|
|
188
|
+
deps.store.writeRun(live)
|
|
189
|
+
log.push(`goal ${live.slug} did not pass verification: ${outcome.lines.slice(0, 2).join(" ").slice(0, 200)}`)
|
|
190
|
+
return { stdout: BLOCK(renderTailNote(live, contract, "verdict", now).slice(0, REASON_CAP)), log }
|
|
191
|
+
}
|
|
192
|
+
case "admit":
|
|
193
|
+
case "schedule": {
|
|
194
|
+
const tail = continuationTail(action)!
|
|
195
|
+
deps.store.writeRun(live)
|
|
196
|
+
const reason = renderTailNote(live, contract, tail, now).slice(0, REASON_CAP)
|
|
197
|
+
log.push(`goal ${live.slug}: decision ${action.kind}; continuing with the ${tail} continuation (${reason.length} chars)`)
|
|
198
|
+
return { stdout: BLOCK(reason), log }
|
|
199
|
+
}
|
|
200
|
+
default: {
|
|
201
|
+
if (action.kind === "stop") setStatus(live, action.status, action.reason)
|
|
202
|
+
deps.store.writeRun(live)
|
|
203
|
+
log.push(`goal ${live.slug}: decision ${action.kind}${action.kind === "stop" ? ` (${action.code})` : ""}; holding the session`)
|
|
204
|
+
return { stdout: HOLD, log }
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
package/src/rpc.ts
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// Shared server↔TUI contract. A plain object (Rpc.define is an identity
|
|
2
|
+
// function), so neither side needs @opencode/plugin at runtime.
|
|
3
|
+
const anyObject = { type: "object", additionalProperties: true } as const
|
|
4
|
+
import type { Handoff } from "./engine/state"
|
|
5
|
+
import type { LaunchView } from "./engine/launches"
|
|
6
|
+
export type { LaunchView } from "./engine/launches"
|
|
7
|
+
// V2 RPC's schema adapter rejects regex patterns. Length is declared here;
|
|
8
|
+
// the host validates hex syntax AND exact target identity before execution.
|
|
9
|
+
const targetObject = { type: "object", properties: { key: { type: "string", minLength: 1, maxLength: 256 }, lock: { type: "string", minLength: 64, maxLength: 64 }, runId: { type: "string", minLength: 1, maxLength: 128 }, sessionID: { type: "string", minLength: 1, maxLength: 256 } }, required: ["key", "lock"], additionalProperties: false } as const
|
|
10
|
+
|
|
11
|
+
export const GoalRpc = {
|
|
12
|
+
id: "opencode-goal",
|
|
13
|
+
methods: {
|
|
14
|
+
provenance: {
|
|
15
|
+
input: { type: "object", properties: { sessionID: { type: "string", minLength: 1, maxLength: 256 } }, additionalProperties: false },
|
|
16
|
+
output: anyObject,
|
|
17
|
+
},
|
|
18
|
+
snapshot: {
|
|
19
|
+
input: { type: "object", properties: { sessionID: { type: "string", minLength: 1, maxLength: 256 } }, required: ["sessionID"], additionalProperties: false },
|
|
20
|
+
output: anyObject,
|
|
21
|
+
},
|
|
22
|
+
list: {
|
|
23
|
+
input: { type: "object", properties: {}, additionalProperties: false },
|
|
24
|
+
output: anyObject,
|
|
25
|
+
},
|
|
26
|
+
details: {
|
|
27
|
+
input: { type: "object", properties: { key: { type: "string", minLength: 1, maxLength: 256 } }, required: ["key"], additionalProperties: false },
|
|
28
|
+
output: anyObject,
|
|
29
|
+
},
|
|
30
|
+
review: {
|
|
31
|
+
input: { type: "object", properties: { sessionID: { type: "string", minLength: 1, maxLength: 256 }, decisionId: { type: "string", minLength: 1, maxLength: 256 } }, required: ["sessionID"], additionalProperties: false },
|
|
32
|
+
output: anyObject,
|
|
33
|
+
},
|
|
34
|
+
manage: {
|
|
35
|
+
input: { type: "object", properties: { sessionID: { type: "string", minLength: 1, maxLength: 256 }, action: { type: "string", enum: ["pause", "resume", "verify", "attach", "abort", "archive"] }, target: targetObject }, required: ["sessionID", "action", "target"], additionalProperties: false },
|
|
36
|
+
output: anyObject,
|
|
37
|
+
},
|
|
38
|
+
act: {
|
|
39
|
+
input: {
|
|
40
|
+
type: "object",
|
|
41
|
+
properties: {
|
|
42
|
+
sessionID: { type: "string", minLength: 1, maxLength: 256 },
|
|
43
|
+
action: { type: "string", enum: ["pause", "resume", "abort", "verify", "approve", "reject", "amend", "archive", "attach", "start", "start-next", "decompose"] },
|
|
44
|
+
arg: { type: "string", maxLength: 2000 },
|
|
45
|
+
decisionId: { type: "string", minLength: 1, maxLength: 256 },
|
|
46
|
+
},
|
|
47
|
+
required: ["sessionID", "action"],
|
|
48
|
+
additionalProperties: false,
|
|
49
|
+
},
|
|
50
|
+
output: anyObject,
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
events: {
|
|
54
|
+
handoff: { schema: { type: "object", properties: { fromSessionID: { type: "string" }, toSessionID: { type: "string" }, slug: { type: "string" }, runId: { type: "string" }, fallback: { type: "string" } }, required: ["fromSessionID", "toSessionID", "slug", "runId"], additionalProperties: false } },
|
|
55
|
+
updated: { schema: { type: "object", properties: { sessionID: { type: "string" }, view: anyObject }, required: ["sessionID"], additionalProperties: true } },
|
|
56
|
+
notice: { schema: { type: "object", properties: { sessionID: { type: "string" }, level: { type: "string" }, text: { type: "string" }, attention: { type: "string" } }, required: ["text"], additionalProperties: true } },
|
|
57
|
+
summary: { schema: { type: "object", properties: { sessionID: { type: "string" }, slug: { type: "string" }, status: { type: "string" }, headline: { type: "string" }, title: { type: "string" }, digest: { type: "string" }, text: { type: "string" } }, required: ["sessionID", "text"], additionalProperties: true } },
|
|
58
|
+
decision: {
|
|
59
|
+
schema: {
|
|
60
|
+
type: "object",
|
|
61
|
+
properties: {
|
|
62
|
+
sessionID: { type: "string" },
|
|
63
|
+
slug: { type: "string" },
|
|
64
|
+
kind: { type: "string", enum: ["needs_review", "paused-after-verdict", "blocked", "budget_limited", "amend-proposed", "supersede-ack", "complete", "start-picker", "owner-operation"] },
|
|
65
|
+
title: { type: "string" },
|
|
66
|
+
message: { type: "string" },
|
|
67
|
+
question: { type: "string" },
|
|
68
|
+
expiresAt: { type: "number" },
|
|
69
|
+
choices: { type: "array", items: { type: "object", properties: { label: { type: "string" }, act: { type: "string" }, arg: { type: "string" }, run: { type: "string" } }, required: ["label"], additionalProperties: true } },
|
|
70
|
+
},
|
|
71
|
+
required: ["sessionID", "kind", "message", "choices"],
|
|
72
|
+
additionalProperties: true,
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
// T075: whichever surface answered a decision (native form or TUI
|
|
76
|
+
// dialog) — live TUIs close their open dialog on it (suppression).
|
|
77
|
+
"decision.resolved": {
|
|
78
|
+
schema: {
|
|
79
|
+
type: "object",
|
|
80
|
+
properties: {
|
|
81
|
+
sessionID: { type: "string" },
|
|
82
|
+
kind: { type: "string" },
|
|
83
|
+
decisionId: { type: "string" },
|
|
84
|
+
via: { type: "string", enum: ["form", "dialog", "form-guidance", "amendment-invalidated"] },
|
|
85
|
+
},
|
|
86
|
+
required: ["sessionID", "kind", "via"],
|
|
87
|
+
additionalProperties: true,
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
"decision.failed": {
|
|
91
|
+
schema: { type: "object", properties: { sessionID: { type: "string" }, decisionId: { type: "string" }, message: { type: "string" } }, required: ["sessionID", "decisionId", "message"], additionalProperties: false },
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
} as const
|
|
95
|
+
|
|
96
|
+
export type CriterionView = {
|
|
97
|
+
id: string
|
|
98
|
+
statement: string
|
|
99
|
+
essential: boolean
|
|
100
|
+
invariant: boolean
|
|
101
|
+
kind: string
|
|
102
|
+
status: "unknown" | "pass" | "fail" | "claimed"
|
|
103
|
+
by?: string
|
|
104
|
+
detail?: string
|
|
105
|
+
at?: number
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export type TimelineEntry = { t: number; kind: string; text: string }
|
|
109
|
+
|
|
110
|
+
export type GoalView = {
|
|
111
|
+
slug: string
|
|
112
|
+
title: string
|
|
113
|
+
outcome: string
|
|
114
|
+
status: string
|
|
115
|
+
reason?: string
|
|
116
|
+
runId: string
|
|
117
|
+
/** Actual run identity for correlating admission attempts, not version labels. */
|
|
118
|
+
sessionID?: string
|
|
119
|
+
lock?: string
|
|
120
|
+
/** T106: the tuple goal_manage accepts, so a caller never loses a round to
|
|
121
|
+
* discovery after a generation change — key, runId, sessionID and lock. */
|
|
122
|
+
target?: GoalTarget
|
|
123
|
+
turn: number
|
|
124
|
+
timeline: TimelineEntry[]
|
|
125
|
+
activeMs: number
|
|
126
|
+
activeSince: number | null
|
|
127
|
+
usage: { tokens: number; cost: number }
|
|
128
|
+
budget: Array<{ name: string; used: number; limit: number }>
|
|
129
|
+
budgetRatio: number
|
|
130
|
+
criteria: CriterionView[]
|
|
131
|
+
steps: Array<{ id: string; title: string; status: string }>
|
|
132
|
+
progress?: { note: string; next?: string; at: number; turn: number }
|
|
133
|
+
verdict?: { passed: boolean; lines: string[]; at: number; turn: number }
|
|
134
|
+
blocker?: { key: string; count: number; reason: string; needs?: string }
|
|
135
|
+
wait?: { until: number; reason: string }
|
|
136
|
+
awaitingUser: boolean
|
|
137
|
+
/** T052: persistent owner-action-required state — stays until the status resolves. */
|
|
138
|
+
actionRequired?: string
|
|
139
|
+
/** T053: what the engine is doing now — the live activity tracer's feed. */
|
|
140
|
+
activity?: { kind: "turn" | "verifying" | "verifier-child" | "cooldown" | "waiting"; since: number; until?: number; detail?: string }
|
|
141
|
+
amendments: number
|
|
142
|
+
flags: number
|
|
143
|
+
updatedAt: number
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export type GoalSummary = {
|
|
147
|
+
slug: string
|
|
148
|
+
title: string
|
|
149
|
+
status: string
|
|
150
|
+
priority?: "low" | "medium" | "high"
|
|
151
|
+
sessionID?: string
|
|
152
|
+
proven: number
|
|
153
|
+
total: number
|
|
154
|
+
terminal?: boolean
|
|
155
|
+
attachable?: boolean
|
|
156
|
+
/** Distinguishes live and archived generations even if their slugs match. */
|
|
157
|
+
key?: string
|
|
158
|
+
archived?: boolean
|
|
159
|
+
valid?: boolean
|
|
160
|
+
runId?: string
|
|
161
|
+
lock?: string
|
|
162
|
+
updatedAt?: number
|
|
163
|
+
outcome?: string
|
|
164
|
+
progress?: RunProgress
|
|
165
|
+
turn?: number
|
|
166
|
+
steps?: Array<{ id: string; title: string; status: string }>
|
|
167
|
+
/** Raw existing contract metadata: never inferred from CLEO task IDs. */
|
|
168
|
+
meta?: Record<string, unknown>
|
|
169
|
+
supersedes?: string
|
|
170
|
+
configuredAgent?: string
|
|
171
|
+
agent?: string
|
|
172
|
+
sessionTitle?: string
|
|
173
|
+
sessionState?: "present" | "elsewhere" | "missing" | "unknown" | "unassigned"
|
|
174
|
+
issues?: string[]
|
|
175
|
+
details?: { criteria: CriterionView[]; constraints: string[]; nonGoals: string[] }
|
|
176
|
+
handoff?: Handoff
|
|
177
|
+
/** Latest exact launch review/admission receipt, distinct from run status. */
|
|
178
|
+
launch?: LaunchView
|
|
179
|
+
}
|
|
180
|
+
export type GoalTarget = { key: string; lock: string; runId?: string; sessionID?: string }
|
|
181
|
+
export type ManageAction = "pause" | "resume" | "verify" | "attach" | "abort" | "archive"
|
|
182
|
+
type RunProgress = { note: string; next?: string; at: number; turn: number }
|