toolroll 0.9.36 → 0.9.37

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 (165) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/dist/assignment-adapters.d.ts +12 -48
  3. package/dist/assignment-adapters.js +33 -43
  4. package/dist/build-review.d.ts +1 -1
  5. package/dist/build-review.js +2 -1
  6. package/dist/chat-acceptance.d.ts +1 -1
  7. package/dist/chat-actions.d.ts +18 -12
  8. package/dist/chat-actions.js +15 -65
  9. package/dist/chat-ask.js +5 -5
  10. package/dist/chat-decide.d.ts +14 -1
  11. package/dist/chat-decide.js +12 -6
  12. package/dist/chat-delivery-state.d.ts +12 -70
  13. package/dist/chat-delivery-state.js +17 -135
  14. package/dist/chat-delivery.js +58 -30
  15. package/dist/chat-flow.js +8 -6
  16. package/dist/chat-inbox.js +1 -1
  17. package/dist/coding-context.d.ts +2 -39
  18. package/dist/coding-context.js +2 -1
  19. package/dist/coding-handoff.d.ts +6 -25
  20. package/dist/coding-handoff.js +11 -5
  21. package/dist/coding-types.d.ts +2 -26
  22. package/dist/coding-workspace.d.ts +2 -0
  23. package/dist/coding-workspace.js +50 -14
  24. package/dist/contracts/acceptance-terms.d.ts +14 -0
  25. package/dist/contracts/acceptance-terms.js +16 -0
  26. package/dist/contracts/chat-actions.d.ts +328 -0
  27. package/dist/contracts/chat-actions.js +133 -0
  28. package/dist/contracts/chat-callback-rows.d.ts +59 -0
  29. package/dist/contracts/chat-callback-rows.js +46 -0
  30. package/dist/contracts/chat-content.d.ts +276 -0
  31. package/dist/contracts/chat-content.js +127 -0
  32. package/dist/contracts/chat-tables.d.ts +6 -0
  33. package/dist/contracts/chat-tables.js +136 -0
  34. package/dist/contracts/coding-context.d.ts +97 -0
  35. package/dist/contracts/coding-context.js +60 -0
  36. package/dist/contracts/coding-handoff.d.ts +44 -0
  37. package/dist/contracts/coding-handoff.js +47 -0
  38. package/dist/contracts/coding-workspace.d.ts +165 -0
  39. package/dist/contracts/coding-workspace.js +71 -0
  40. package/dist/contracts/contract.d.ts +7 -2
  41. package/dist/contracts/contract.js +21 -12
  42. package/dist/contracts/decision.d.ts +45 -0
  43. package/dist/contracts/decision.js +45 -0
  44. package/dist/contracts/discord-callback.d.ts +45 -0
  45. package/dist/contracts/discord-callback.js +34 -0
  46. package/dist/contracts/flow-card.d.ts +50 -0
  47. package/dist/contracts/flow-card.js +46 -0
  48. package/dist/contracts/flow-send.d.ts +119 -0
  49. package/dist/contracts/flow-send.js +112 -0
  50. package/dist/contracts/flow.d.ts +5 -0
  51. package/dist/contracts/flow.js +5 -0
  52. package/dist/contracts/gateway-tools.d.ts +374 -0
  53. package/dist/contracts/gateway-tools.js +94 -0
  54. package/dist/contracts/lead-context.d.ts +165 -0
  55. package/dist/contracts/lead-context.js +73 -0
  56. package/dist/contracts/lead-tools.d.ts +1905 -0
  57. package/dist/contracts/lead-tools.js +265 -0
  58. package/dist/contracts/memory-pass.d.ts +158 -0
  59. package/dist/contracts/memory-pass.js +102 -0
  60. package/dist/contracts/project-knowledge.d.ts +126 -0
  61. package/dist/contracts/project-knowledge.js +93 -0
  62. package/dist/contracts/project-memory.d.ts +117 -0
  63. package/dist/contracts/project-memory.js +51 -0
  64. package/dist/contracts/project-skills.d.ts +121 -0
  65. package/dist/contracts/project-skills.js +76 -0
  66. package/dist/contracts/review-findings.d.ts +46 -0
  67. package/dist/contracts/review-findings.js +48 -0
  68. package/dist/contracts/route.d.ts +484 -0
  69. package/dist/contracts/route.js +128 -0
  70. package/dist/contracts/scope.d.ts +105 -0
  71. package/dist/contracts/scope.js +114 -0
  72. package/dist/contracts/scout-report.d.ts +234 -0
  73. package/dist/contracts/scout-report.js +141 -0
  74. package/dist/contracts/slack-callback.d.ts +43 -0
  75. package/dist/contracts/slack-callback.js +35 -0
  76. package/dist/contracts/sort-answer.d.ts +83 -0
  77. package/dist/contracts/sort-answer.js +77 -0
  78. package/dist/contracts/stage-output.d.ts +127 -0
  79. package/dist/contracts/stage-output.js +166 -0
  80. package/dist/contracts/task-sizing.d.ts +24 -0
  81. package/dist/contracts/task-sizing.js +28 -0
  82. package/dist/contracts/teammate-turn.d.ts +43 -0
  83. package/dist/contracts/teammate-turn.js +51 -0
  84. package/dist/contracts/teams-callback.d.ts +14 -0
  85. package/dist/contracts/teams-callback.js +14 -0
  86. package/dist/contracts/telegram-callback.d.ts +120 -0
  87. package/dist/contracts/telegram-callback.js +97 -0
  88. package/dist/decision.d.ts +11 -20
  89. package/dist/decision.js +86 -167
  90. package/dist/desktop-update.d.ts +2 -2
  91. package/dist/discord-chat.js +31 -20
  92. package/dist/flow-engine.js +14 -17
  93. package/dist/flow-items.d.ts +4 -13
  94. package/dist/flow-items.js +1 -15
  95. package/dist/flow-send.d.ts +6 -37
  96. package/dist/flow-send.js +18 -14
  97. package/dist/flow-share.js +7 -1
  98. package/dist/flow-sort.d.ts +7 -26
  99. package/dist/flow-sort.js +25 -20
  100. package/dist/flow-steps.js +3 -2
  101. package/dist/flows.d.ts +2 -4
  102. package/dist/flows.js +9 -5
  103. package/dist/invoke.js +4 -4
  104. package/dist/lead-context.d.ts +4 -2
  105. package/dist/lead-context.js +24 -17
  106. package/dist/mate-tools.d.ts +4 -0
  107. package/dist/mate-tools.js +108 -275
  108. package/dist/mcp-connect.d.ts +24 -4
  109. package/dist/mcp-connect.js +78 -15
  110. package/dist/mcp.js +84 -216
  111. package/dist/memory-pass.d.ts +17 -42
  112. package/dist/memory-pass.js +54 -26
  113. package/dist/phase-routing.d.ts +19 -72
  114. package/dist/phase-routing.js +34 -164
  115. package/dist/project-knowledge.d.ts +16 -35
  116. package/dist/project-knowledge.js +42 -21
  117. package/dist/project-memory.d.ts +2 -28
  118. package/dist/project-memory.js +28 -13
  119. package/dist/project-skills.d.ts +16 -38
  120. package/dist/project-skills.js +41 -32
  121. package/dist/project-tools.d.ts +1 -0
  122. package/dist/project-tools.js +4 -4
  123. package/dist/provider.d.ts +2 -0
  124. package/dist/provider.js +4 -16
  125. package/dist/result-shots.d.ts +1 -1
  126. package/dist/result-shots.js +2 -1
  127. package/dist/review-switch.d.ts +3 -8
  128. package/dist/reviewer.d.ts +8 -12
  129. package/dist/reviewer.js +11 -35
  130. package/dist/scope.d.ts +21 -43
  131. package/dist/scope.js +42 -128
  132. package/dist/scout-report.d.ts +9 -194
  133. package/dist/scout-report.js +113 -264
  134. package/dist/scout.js +15 -11
  135. package/dist/serve.js +9 -2
  136. package/dist/setup-guide.d.ts +2 -2
  137. package/dist/slack-chat.js +26 -20
  138. package/dist/slack-state.js +2 -1
  139. package/dist/store.d.ts +9 -32
  140. package/dist/store.js +18 -15
  141. package/dist/structured-output.js +2 -1
  142. package/dist/subscription-chat.d.ts +4 -0
  143. package/dist/subscription-chat.js +4 -1
  144. package/dist/task-review-brief.d.ts +1 -7
  145. package/dist/task-review-brief.js +6 -6
  146. package/dist/task-sizing.d.ts +3 -27
  147. package/dist/task-sizing.js +11 -26
  148. package/dist/task-text.d.ts +0 -5
  149. package/dist/task-text.js +1 -3
  150. package/dist/teammate-question.js +7 -5
  151. package/dist/teammate-work.js +9 -5
  152. package/dist/teammates.d.ts +13 -64
  153. package/dist/teammates.js +32 -33
  154. package/dist/teams-chat.js +20 -11
  155. package/dist/telegram-flow.js +4 -3
  156. package/dist/telegram-mate.d.ts +4 -14
  157. package/dist/telegram-mate.js +8 -7
  158. package/dist/telegram-team.d.ts +2 -19
  159. package/dist/telegram.d.ts +1 -1
  160. package/dist/telegram.js +52 -42
  161. package/dist/text-limits.d.ts +142 -1
  162. package/dist/text-limits.js +144 -1
  163. package/dist/tools-ui.d.ts +2 -1
  164. package/dist/tools-ui.js +7 -4
  165. package/package.json +1 -1
@@ -1,43 +1,6 @@
1
1
  import type { Store } from './store.js';
2
- export type CodingContextMetadata = {
3
- version: 1;
4
- repo: string;
5
- identity: string;
6
- baseRevision: string;
7
- knowledge: {
8
- revision: number;
9
- selectionSha256: string;
10
- references: {
11
- id: string;
12
- title: string;
13
- path: string | null;
14
- sourceSha: string | null;
15
- sourceRevision: string | null;
16
- }[];
17
- omitted: {
18
- title: string;
19
- reason: string;
20
- }[];
21
- };
22
- skills: {
23
- revision: number;
24
- packages: {
25
- name: string;
26
- sha256: string;
27
- skillFile: string;
28
- }[];
29
- };
30
- directory: string | null;
31
- files: {
32
- path: string;
33
- sha256: string;
34
- }[];
35
- };
36
- export type CodingContext = {
37
- text: string;
38
- metadata: CodingContextMetadata;
39
- sha256: string;
40
- };
2
+ import { type CodingContext } from './contracts/coding-context.js';
3
+ export type { CodingContext, CodingContextMetadata } from './contracts/coding-context.js';
41
4
  /**
42
5
  * Call before thread/start with the canonical project and the exact worktree
43
6
  * base. Persist the returned object with the coding session and reuse it on
@@ -5,6 +5,7 @@ import { basename, dirname, join, resolve, relative, isAbsolute, sep } from 'nod
5
5
  import { KNOWLEDGE_GUIDANCE, selectProjectKnowledge } from './project-knowledge.js';
6
6
  import { materializeProjectSkills, PROJECT_SKILLS_GUIDANCE, selectProjectSkills } from './project-skills.js';
7
7
  import { identityMatches, learningIdentity, learningSha } from './project-learning.js';
8
+ import { CODING_CONTEXT_VERSION } from './contracts/coding-context.js';
8
9
  function freeze(value) {
9
10
  if (value !== null && typeof value === 'object' && !Object.isFrozen(value)) {
10
11
  for (const child of Object.values(value))
@@ -70,7 +71,7 @@ export function prepareCodingContext(store, args) {
70
71
  directory,
71
72
  files: captured.skills.packages.flatMap(skill => skill.files.map(file => ({ path: `${skill.name}/${file.path}`, sha256: fileDigest(Buffer.from(file.base64, 'base64')) }))),
72
73
  };
73
- return freeze({ text, metadata, sha256: digest(text, metadata) });
74
+ return freeze({ version: CODING_CONTEXT_VERSION, text, metadata, sha256: digest(text, metadata) });
74
75
  }
75
76
  /** Integrity check for a persisted capture. The caller must separately recheck current account access. */
76
77
  export function verifyCodingContext(context, repo, baseRevision) {
@@ -1,28 +1,7 @@
1
- import { type AcceptanceCriterion } from './scope.js';
1
+ import { type CodingHandoffReceipt } from './contracts/coding-handoff.js';
2
2
  import type { Store } from './store.js';
3
- export type CodingHandoffPreview = {
4
- sessionId: string;
5
- repo: string;
6
- base: string;
7
- candidate: string;
8
- title: string;
9
- originalPrompt: string;
10
- changedPaths: string[];
11
- };
12
- type ReceiptTerms = CodingHandoffPreview & {
13
- version: 1;
14
- identity: string;
15
- actor: string;
16
- generation: number;
17
- goal: string;
18
- acceptance: AcceptanceCriterion[];
19
- outOfScope: string;
20
- };
21
- export type CodingHandoffReceipt = ReceiptTerms & {
22
- id: string;
23
- taskId: string;
24
- branch: string;
25
- };
3
+ export type { CodingHandoffReceipt } from './contracts/coding-handoff.js';
4
+ export type CodingHandoffPreview = Pick<CodingHandoffReceipt, 'sessionId' | 'repo' | 'base' | 'candidate' | 'title' | 'originalPrompt' | 'changedPaths'>;
26
5
  export type CodingHandoffInput = {
27
6
  sessionId: string;
28
7
  repo: string;
@@ -42,6 +21,9 @@ export declare function codingHandoffPreview(store: Store, input: {
42
21
  sessionId: string;
43
22
  actor: string;
44
23
  }): CodingHandoffPreview;
24
+ /** The saved bytes are verified before they are parsed; identity and branch are checked on the parsed terms. */
25
+ export declare function parseReceipt(row: Record<string, unknown>): CodingHandoffReceipt;
26
+ export declare function sealPayload(receipt: CodingHandoffReceipt, scopeDigest: string): string;
45
27
  /** Returns null only for ordinary tasks. A coding marker always requires its receipt. */
46
28
  export declare function readCodingHandoff(store: Store, taskId: string): Handoff | null;
47
29
  /** No agent dispatch, approval, push, or modification of an existing Git ref. */
@@ -54,4 +36,3 @@ export declare function verifyCodingHandoffBase(store: Store, input: {
54
36
  branch: string;
55
37
  head: string;
56
38
  }): void;
57
- export {};
@@ -10,6 +10,8 @@ import { parseAcceptanceCriteria, proposeGuarded } from './scope.js';
10
10
  import { validateTaskText } from './task-text.js';
11
11
  import { PROOF_LIMITS } from './proof.js';
12
12
  import { readPreparedEvidence } from './prepared-evidence.js';
13
+ import { parseCodingHandoffReceipt } from './contracts/coding-handoff.js';
14
+ import { parseCodingSessionDocument } from './contracts/coding-workspace.js';
13
15
  import { BRANCH_PREFIXES, taskBranch } from './names.js';
14
16
  const PREFIX = 'coding:';
15
17
  const SHA = /^[a-f0-9]{40}$/;
@@ -84,7 +86,7 @@ function sessionFor(db, store, sessionId, actor) {
84
86
  const row = db.prepare('SELECT owner,generation,repo,document FROM coding_session WHERE id=?').get(sessionId);
85
87
  if (!row || row['owner'] !== actor)
86
88
  throw Error('This coding session is not available to your account.');
87
- const session = JSON.parse(String(row['document']));
89
+ const session = parseCodingSessionDocument(String(row['document']));
88
90
  const generation = actorGeneration(store, actor, String(row['repo']));
89
91
  if (row['generation'] !== generation || session.id !== sessionId || session.owner !== actor || session.generation !== generation || session.repo !== row['repo'])
90
92
  throw Error('The coding session no longer matches your account or project.');
@@ -132,18 +134,22 @@ function makeReceipt(terms) {
132
134
  const id = learningSha(JSON.stringify(terms)).slice(0, 32), taskId = `coding-review-${id}`;
133
135
  return { ...terms, id, taskId, branch: taskBranch(taskId) };
134
136
  }
135
- function parseReceipt(row) {
137
+ /** The saved bytes are verified before they are parsed; identity and branch are checked on the parsed terms. */
138
+ export function parseReceipt(row) {
136
139
  const payload = String(row['payload']);
137
140
  if (learningSha(payload) !== row['sha'])
138
141
  throw Error('The coding handoff receipt could not be verified.');
139
- const receipt = JSON.parse(payload);
142
+ const read = parseCodingHandoffReceipt(payload);
143
+ if (!read.ok)
144
+ throw Error(`The coding handoff receipt could not be read: ${read.issues.map(issue => issue.line).join('; ')}`);
145
+ const receipt = read.value;
140
146
  const { id, taskId, branch, ...terms } = receipt;
141
147
  const expected = makeReceipt(terms);
142
- if (receipt.version !== 1 || row['id'] !== id || id !== expected.id || taskId !== expected.taskId || !BRANCH_PREFIXES.some(prefix => branch === `${prefix}${taskId}`) || !SHA.test(receipt.base) || !SHA.test(receipt.candidate))
148
+ if (row['id'] !== id || id !== expected.id || taskId !== expected.taskId || !BRANCH_PREFIXES.some(prefix => branch === `${prefix}${taskId}`) || !SHA.test(receipt.base) || !SHA.test(receipt.candidate))
143
149
  throw Error('The coding handoff receipt identity could not be verified.');
144
150
  return receipt;
145
151
  }
146
- function sealPayload(receipt, scopeDigest) {
152
+ export function sealPayload(receipt, scopeDigest) {
147
153
  return JSON.stringify({ version: 1, id: receipt.id, receiptSha256: learningSha(JSON.stringify(receipt)), scopeDigest });
148
154
  }
149
155
  function verifySeal(db, receipt, digest) {
@@ -1,29 +1,5 @@
1
- import type { CodingContext } from './coding-context.js';
2
- export type CodingStatus = 'starting' | 'ready' | 'working' | 'needs-input' | 'stopping' | 'interrupted' | 'failed' | 'uncertain' | 'closed';
3
- export type CodingSession = {
4
- id: string;
5
- owner: string;
6
- generation: number;
7
- repo: string;
8
- title: string;
9
- provider: 'codex';
10
- model: string | null;
11
- branch: string;
12
- base: string;
13
- worktree: string;
14
- nativeThreadId: string | null;
15
- turnId: string | null;
16
- status: CodingStatus;
17
- error: string | null;
18
- createdAt: string;
19
- updatedAt: string;
20
- deliveryReviewRequired?: boolean;
21
- initialRequest?: {
22
- requestId: string;
23
- prompt: string;
24
- };
25
- context?: CodingContext;
26
- };
1
+ export type { CodingSession, CodingStatus } from './contracts/coding-workspace.js';
2
+ import type { CodingSession } from './contracts/coding-workspace.js';
27
3
  export type CodingItem = {
28
4
  id: string;
29
5
  type: string;
@@ -60,6 +60,8 @@ export declare class CodingWorkspace {
60
60
  constructor(options: Options);
61
61
  private authorize;
62
62
  private read;
63
+ /** Preserve legacy reads, but never mutate an unidentified row or save an unreadable record's error projection. */
64
+ private catalogRow;
63
65
  private admit;
64
66
  private releaseOperation;
65
67
  list(actor: CodingActor): CodingSession[];
@@ -9,6 +9,7 @@ import { run } from './exec.js';
9
9
  import { processMayBeAlive } from './process-liveness.js';
10
10
  import { createCodexCodingProvider } from './coding-provider.js';
11
11
  import { verifyCodingContext } from './coding-context.js';
12
+ import { codingSessionDocument, parseCodingSessionDocument } from './contracts/coding-workspace.js';
12
13
  export class CodingActionError extends Error {
13
14
  delivery;
14
15
  sessionId;
@@ -77,8 +78,10 @@ export class CodingWorkspace {
77
78
  const clean = !prior || prior['clean'] === 1;
78
79
  this.recoveryRequired = !clean;
79
80
  this.db.prepare('INSERT OR REPLACE INTO coding_owner VALUES(1,?,?,?,?)').run(this.owner, process.pid, prior?.['native_pid'] ?? null, clean ? 1 : 0);
80
- for (const row of this.db.prepare('SELECT document FROM coding_session').all()) {
81
- const session = JSON.parse(String(row['document']));
81
+ for (const row of this.db.prepare('SELECT * FROM coding_session').all()) {
82
+ const { session, usable } = this.catalogRow(row);
83
+ if (!usable)
84
+ continue;
82
85
  if (session.status !== 'closed' && (!clean || busy.has(session.status)))
83
86
  this.save({ ...session, status: clean ? 'interrupted' : 'uncertain', turnId: null, error: clean ? null : 'The previous server stopped without a verified process exit. Work is preserved; session recovery needs a process check before resuming.' });
84
87
  }
@@ -99,7 +102,23 @@ export class CodingWorkspace {
99
102
  const row = this.db.prepare('SELECT document FROM coding_session WHERE id=?').get(id);
100
103
  if (!row)
101
104
  throw Error('Coding session unavailable.');
102
- return JSON.parse(String(row['document']));
105
+ return parseCodingSessionDocument(String(row['document']));
106
+ }
107
+ /** Preserve legacy reads, but never mutate an unidentified row or save an unreadable record's error projection. */
108
+ catalogRow(row) {
109
+ try {
110
+ const session = parseCodingSessionDocument(String(row['document']));
111
+ const usable = session.id === row['id'] && session.owner === row['owner'] && session.generation === row['generation'] && session.repo === row['repo'];
112
+ return { session, usable };
113
+ }
114
+ catch (error) {
115
+ return { usable: false, session: {
116
+ id: String(row['id']), owner: String(row['owner']), generation: Number(row['generation']), repo: String(row['repo']),
117
+ title: 'Unreadable coding session', provider: 'codex', model: null, branch: '', base: '', worktree: '',
118
+ nativeThreadId: null, turnId: null, status: 'failed', createdAt: '', updatedAt: '',
119
+ error: error instanceof Error ? error.message : 'The saved coding session could not be read.',
120
+ } };
121
+ }
103
122
  }
104
123
  admit() {
105
124
  if (this.closed || this.closing || this.persistenceFailed)
@@ -116,7 +135,7 @@ export class CodingWorkspace {
116
135
  }
117
136
  }
118
137
  list(actor) {
119
- return this.db.prepare('SELECT document FROM coding_session WHERE owner=? AND generation=? ORDER BY rowid DESC').all(actor.name, actor.generation).map(row => JSON.parse(String(row['document'])));
138
+ return this.db.prepare('SELECT * FROM coding_session WHERE owner=? AND generation=? ORDER BY rowid DESC').all(actor.name, actor.generation).map(row => this.catalogRow(row).session);
120
139
  }
121
140
  /** Inspect a fixed metadata window before loading admitted documents.
122
141
  * A filtered window can be short without proving the entire list empty. */
@@ -127,13 +146,18 @@ export class CodingWorkspace {
127
146
  const window = rows.slice(0, 100);
128
147
  const admitted = window.filter(row => options.authorized(String(row['repo'])));
129
148
  const selected = admitted.slice(0, Math.min(100, Math.max(1, options.limit)));
130
- return { sessions: selected.map(row => this.get(String(row['id']), actor)), truncated: rows.length > window.length || admitted.length > selected.length };
149
+ const sessions = selected.map(row => {
150
+ const session = this.catalogRow(this.db.prepare('SELECT * FROM coding_session WHERE id=? AND owner=? AND generation=?').get(String(row['id']), actor.name, actor.generation)).session;
151
+ this.authorize(actor, session.repo);
152
+ return session;
153
+ });
154
+ return { sessions, truncated: rows.length > window.length || admitted.length > selected.length };
131
155
  }
132
156
  get(id, actor) {
133
157
  const row = this.db.prepare('SELECT document FROM coding_session WHERE id=? AND owner=? AND generation=?').get(id, actor.name, actor.generation);
134
158
  if (!row)
135
159
  throw Error('This coding session is not available to your account.');
136
- const session = JSON.parse(String(row['document']));
160
+ const session = parseCodingSessionDocument(String(row['document']));
137
161
  this.authorize(actor, session.repo);
138
162
  return session;
139
163
  }
@@ -204,13 +228,16 @@ export class CodingWorkspace {
204
228
  }
205
229
  save(session) {
206
230
  session.updatedAt = now();
207
- this.db.prepare('INSERT INTO coding_session(id,owner,generation,repo,document) VALUES(?,?,?,?,?) ON CONFLICT(id) DO UPDATE SET document=excluded.document,revision=revision+1').run(session.id, session.owner, session.generation, session.repo, JSON.stringify(session));
231
+ this.db.prepare('INSERT INTO coding_session(id,owner,generation,repo,document) VALUES(?,?,?,?,?) ON CONFLICT(id) DO UPDATE SET document=excluded.document,revision=revision+1').run(session.id, session.owner, session.generation, session.repo, codingSessionDocument(session));
208
232
  }
209
233
  byThread(thread) {
210
234
  if (typeof thread !== 'string')
211
235
  return null;
212
- const row = this.db.prepare("SELECT document FROM coding_session WHERE json_extract(document,'$.nativeThreadId')=?").get(thread);
213
- return row ? JSON.parse(String(row['document'])) : null;
236
+ const row = this.db.prepare("SELECT * FROM coding_session WHERE json_extract(document,'$.nativeThreadId')=?").get(thread);
237
+ if (!row)
238
+ return null;
239
+ const { session, usable } = this.catalogRow(row);
240
+ return usable ? session : null;
214
241
  }
215
242
  putItem(session, item) {
216
243
  // Re-reading unchanged native history must not invalidate the request
@@ -285,8 +312,10 @@ export class CodingWorkspace {
285
312
  if (event.kind === 'exit') {
286
313
  this.flush();
287
314
  this.loaded.clear();
288
- for (const row of this.db.prepare('SELECT document FROM coding_session').all()) {
289
- const session = JSON.parse(String(row['document']));
315
+ for (const row of this.db.prepare('SELECT * FROM coding_session').all()) {
316
+ const { session, usable } = this.catalogRow(row);
317
+ if (!usable)
318
+ continue;
290
319
  if (busy.has(session.status))
291
320
  this.save({ ...session, status: 'uncertain', error: 'The agent connection closed. Work is preserved; delivery and process exit need to be checked before continuing.' });
292
321
  }
@@ -522,7 +551,12 @@ export class CodingWorkspace {
522
551
  this.operations.add(id);
523
552
  try {
524
553
  if (this.provider) {
525
- const active = this.db.prepare('SELECT document FROM coding_session').all().some(row => busy.has(JSON.parse(String(row['document'])).status));
554
+ const active = this.db.prepare('SELECT * FROM coding_session').all().some(row => {
555
+ const { session, usable } = this.catalogRow(row);
556
+ if (!usable)
557
+ throw new CodingActionError('A saved coding session could not be checked. Repair its record before reconnecting the agent.', 'rejected', id);
558
+ return busy.has(session.status);
559
+ });
526
560
  if (active)
527
561
  throw new CodingActionError('Another coding turn is still active. Wait for it to finish or stop it before reconnecting the agent.', 'rejected', id);
528
562
  await this.provider.close();
@@ -754,8 +788,10 @@ export class CodingWorkspace {
754
788
  this.db.close();
755
789
  return;
756
790
  }
757
- for (const row of this.db.prepare('SELECT document FROM coding_session').all()) {
758
- const session = JSON.parse(String(row['document']));
791
+ for (const row of this.db.prepare('SELECT * FROM coding_session').all()) {
792
+ const { session, usable } = this.catalogRow(row);
793
+ if (!usable)
794
+ continue;
759
795
  const uncertain = this.db.prepare("SELECT 1 FROM coding_submission WHERE session=? AND status IN ('preparing','pending','uncertain')").get(session.id);
760
796
  if (!uncertain && (busy.has(session.status) || session.status === 'uncertain'))
761
797
  this.save({ ...session, status: 'interrupted', turnId: null, error: null });
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The acceptance rubric's vocabulary and limits: the evidence kinds a criterion may require, and how many criteria
3
+ * and bytes a rubric may hold. A leaf with no imports but TEXT_LIMITS, because the plan contract (src/contracts/plan.ts)
4
+ * reads these through scope.ts while scope.ts reads the plan contract's criterion: kept here, both load in either order.
5
+ */
6
+ export declare const EVIDENCE_KINDS: readonly ["check", "screenshot", "changed-path", "manual-review"];
7
+ export type EvidenceKind = (typeof EVIDENCE_KINDS)[number];
8
+ export declare const ACCEPTANCE_LIMITS: {
9
+ readonly criteria: 12;
10
+ readonly id: 40;
11
+ readonly statement: 1000;
12
+ readonly how: 500;
13
+ readonly evidenceKinds: 4;
14
+ };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The acceptance rubric's vocabulary and limits: the evidence kinds a criterion may require, and how many criteria
3
+ * and bytes a rubric may hold. A leaf with no imports but TEXT_LIMITS, because the plan contract (src/contracts/plan.ts)
4
+ * reads these through scope.ts while scope.ts reads the plan contract's criterion: kept here, both load in either order.
5
+ */
6
+ import { TEXT_LIMITS } from "../text-limits.js";
7
+ export const EVIDENCE_KINDS = ["check", "screenshot", "changed-path", "manual-review"];
8
+ export const ACCEPTANCE_LIMITS = {
9
+ criteria: 12,
10
+ id: TEXT_LIMITS.acceptanceIdBytes,
11
+ // A statement is one testable outcome; three hundred bytes forced people to
12
+ // drop the qualifying clause that made it testable.
13
+ statement: TEXT_LIMITS.acceptanceStatementBytes,
14
+ how: TEXT_LIMITS.acceptanceHowBytes,
15
+ evidenceKinds: EVIDENCE_KINDS.length,
16
+ };