@hobin/developer 0.1.6 → 0.1.7
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/README.md +110 -92
- package/SOURCES.md +8 -8
- package/extensions/developer.ts +1689 -1441
- package/extensions/machine.ts +418 -286
- package/extensions/references/behavior-preserving-structural-change.md +14 -13
- package/extensions/state.ts +463 -419
- package/extensions/tool-policy.ts +70 -64
- package/extensions/tui.ts +1666 -910
- package/package.json +1 -1
- package/skills/sketch/references/responsibility-and-variation.md +1 -1
package/extensions/state.ts
CHANGED
|
@@ -1,511 +1,555 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
applyDeveloperEvent,
|
|
3
|
+
developerSnapshot,
|
|
4
|
+
initialState,
|
|
5
|
+
} from "./machine.ts";
|
|
2
6
|
|
|
3
7
|
export {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
applyDeveloperEvent,
|
|
9
|
+
canApplyDeveloperEvent,
|
|
10
|
+
developerMachine,
|
|
11
|
+
developerSnapshot,
|
|
12
|
+
initialState,
|
|
9
13
|
} from "./machine.ts";
|
|
10
14
|
|
|
11
|
-
export const PROTOCOL = "developer/
|
|
12
|
-
export const
|
|
13
|
-
export const OLDER_PROTOCOL = "developer/v2" as const;
|
|
14
|
-
export const LEGACY_PROTOCOL = "developer/v1" as const;
|
|
15
|
-
export const MODE_ENTRY = "developer.mode" as const;
|
|
15
|
+
export const PROTOCOL = "developer/v5" as const;
|
|
16
|
+
export const ACTIVATION_ENTRY = "developer.activation" as const;
|
|
16
17
|
export const FOCUS_ENTRY = "developer.question-focus" as const;
|
|
17
18
|
export const ROUTE_TOOL = "developer_route_question" as const;
|
|
18
19
|
export const JUDGMENT_TOOL = "developer_record_judgment" as const;
|
|
19
|
-
export const LEGACY_ROUTE_TOOL = "route_question" as const;
|
|
20
|
-
export const LEGACY_JUDGMENT_TOOL = "record_judgment" as const;
|
|
21
20
|
export const MAX_RESPONSE_FIELDS = 20;
|
|
22
21
|
export const MAX_RESPONSE_OPTIONS = 20;
|
|
23
22
|
export const MAX_RESPONSE_IDENTIFIER_CHARS = 64;
|
|
24
23
|
export const MAX_RESPONSE_TEXT_CHARS = 2_000;
|
|
25
24
|
|
|
26
|
-
export type
|
|
27
|
-
|
|
25
|
+
export type JudgmentStatus =
|
|
26
|
+
| "resolved"
|
|
27
|
+
| "needs-evidence"
|
|
28
|
+
| "not-applicable"
|
|
29
|
+
| "blocked";
|
|
28
30
|
export type PendingQuestionStatus = "open" | "blocked";
|
|
29
|
-
export type QuestionResolutionOwner =
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
export
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
export type QuestionResolutionOwner =
|
|
32
|
+
| "agent"
|
|
33
|
+
| "user"
|
|
34
|
+
| "environment"
|
|
35
|
+
| "unknown";
|
|
36
|
+
export type QuestionGate =
|
|
37
|
+
| "none"
|
|
38
|
+
| "before-implementation"
|
|
39
|
+
| "before-completion";
|
|
40
|
+
export type QuestionUpdateStatus =
|
|
41
|
+
| "resolved"
|
|
42
|
+
| "not-applicable"
|
|
43
|
+
| "open"
|
|
44
|
+
| "blocked";
|
|
45
|
+
export type ImplementationProfile =
|
|
46
|
+
| "ordinary"
|
|
47
|
+
| "behavior-preserving-structure";
|
|
48
|
+
|
|
49
|
+
export interface ActivationEvent {
|
|
50
|
+
protocol: typeof PROTOCOL;
|
|
51
|
+
kind: "activation";
|
|
52
|
+
enabled: boolean;
|
|
38
53
|
}
|
|
39
54
|
|
|
40
55
|
export interface FocusEvent {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
56
|
+
protocol: typeof PROTOCOL;
|
|
57
|
+
kind: "focus";
|
|
58
|
+
questionId: string;
|
|
44
59
|
}
|
|
45
60
|
|
|
46
|
-
export interface
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
61
|
+
export interface ImplementationStepContract {
|
|
62
|
+
movement: string;
|
|
63
|
+
stopCondition: string;
|
|
64
|
+
verification: string;
|
|
50
65
|
}
|
|
51
66
|
|
|
52
67
|
export interface RouteAlternative {
|
|
53
|
-
|
|
54
|
-
|
|
68
|
+
target: string;
|
|
69
|
+
reason: string;
|
|
55
70
|
}
|
|
56
71
|
|
|
57
72
|
export interface RouteEvent {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
73
|
+
protocol: typeof PROTOCOL;
|
|
74
|
+
kind: "route";
|
|
75
|
+
routeId: string;
|
|
76
|
+
question: string;
|
|
77
|
+
target: string;
|
|
78
|
+
reason: string;
|
|
79
|
+
knownEvidence: string[];
|
|
80
|
+
consideredAlternatives: RouteAlternative[];
|
|
81
|
+
targetQuestionId?: string;
|
|
82
|
+
methodLocation?: string;
|
|
83
|
+
executionProfile?: ImplementationProfile;
|
|
84
|
+
implementationStep?: ImplementationStepContract;
|
|
70
85
|
}
|
|
71
86
|
|
|
72
87
|
export interface ChoiceResponseOption {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
88
|
+
value: string;
|
|
89
|
+
label: string;
|
|
90
|
+
description?: string;
|
|
91
|
+
detailPrompt?: string;
|
|
77
92
|
}
|
|
78
93
|
|
|
79
94
|
export interface ChoiceResponseField {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
95
|
+
id: string;
|
|
96
|
+
prompt: string;
|
|
97
|
+
description?: string;
|
|
98
|
+
options: ChoiceResponseOption[];
|
|
84
99
|
}
|
|
85
100
|
|
|
86
101
|
export interface ChoiceResponseSpec {
|
|
87
|
-
|
|
88
|
-
|
|
102
|
+
kind: "choice-form";
|
|
103
|
+
fields: ChoiceResponseField[];
|
|
89
104
|
}
|
|
90
105
|
|
|
91
106
|
export interface PendingQuestion {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
107
|
+
id: string;
|
|
108
|
+
question: string;
|
|
109
|
+
context?: string;
|
|
110
|
+
responseSpec?: ChoiceResponseSpec;
|
|
111
|
+
status: PendingQuestionStatus;
|
|
112
|
+
resolutionOwner: QuestionResolutionOwner;
|
|
113
|
+
gate: QuestionGate;
|
|
114
|
+
resolutionCriteria: string;
|
|
115
|
+
sourceRouteId: string;
|
|
101
116
|
}
|
|
102
117
|
|
|
103
118
|
export interface QuestionUpdate {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
119
|
+
questionId: string;
|
|
120
|
+
status: QuestionUpdateStatus;
|
|
121
|
+
result: string;
|
|
122
|
+
basis: string[];
|
|
108
123
|
}
|
|
109
124
|
|
|
110
125
|
export interface JudgmentEvent {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export type DeveloperEvent =
|
|
126
|
+
protocol: typeof PROTOCOL;
|
|
127
|
+
kind: "judgment";
|
|
128
|
+
routeId: string;
|
|
129
|
+
question: string;
|
|
130
|
+
target: string;
|
|
131
|
+
status: JudgmentStatus;
|
|
132
|
+
result: string;
|
|
133
|
+
basis: string[];
|
|
134
|
+
openedQuestions: PendingQuestion[];
|
|
135
|
+
questionUpdates: QuestionUpdate[];
|
|
136
|
+
artifacts: string[];
|
|
137
|
+
changedArtifacts: boolean;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export type DeveloperEvent =
|
|
141
|
+
| ActivationEvent
|
|
142
|
+
| FocusEvent
|
|
143
|
+
| RouteEvent
|
|
144
|
+
| JudgmentEvent;
|
|
126
145
|
|
|
127
146
|
export interface DeveloperState {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
147
|
+
enabled: boolean;
|
|
148
|
+
activeRoute?: RouteEvent;
|
|
149
|
+
lastRoute?: RouteEvent;
|
|
150
|
+
lastJudgment?: JudgmentEvent;
|
|
151
|
+
routeHistory: RouteEvent[];
|
|
152
|
+
judgmentHistory: JudgmentEvent[];
|
|
153
|
+
pendingQuestions: PendingQuestion[];
|
|
154
|
+
focusedQuestionId?: string;
|
|
155
|
+
rerouteRequired: boolean;
|
|
156
|
+
implementationFramingRequired: boolean;
|
|
157
|
+
verificationRequired: boolean;
|
|
139
158
|
}
|
|
140
159
|
|
|
141
160
|
export type ProtocolState =
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
161
|
+
| "idle"
|
|
162
|
+
| "needs-judgment"
|
|
163
|
+
| "needs-evidence"
|
|
164
|
+
| "needs-answer"
|
|
165
|
+
| "needs-routing"
|
|
166
|
+
| "needs-verification"
|
|
167
|
+
| "blocked";
|
|
149
168
|
|
|
150
169
|
export function protocolState(state: DeveloperState): ProtocolState {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
170
|
+
const snapshot = developerSnapshot(state);
|
|
171
|
+
if (!snapshot.matches({ route: "idle" })) return "needs-judgment";
|
|
172
|
+
if (
|
|
173
|
+
snapshot.hasTag("blocks-implementation") ||
|
|
174
|
+
state.pendingQuestions.some((question) => question.status === "blocked")
|
|
175
|
+
)
|
|
176
|
+
return "blocked";
|
|
177
|
+
if (
|
|
178
|
+
state.pendingQuestions.some(
|
|
179
|
+
(question) => question.resolutionOwner === "user",
|
|
180
|
+
)
|
|
181
|
+
)
|
|
182
|
+
return "needs-answer";
|
|
183
|
+
if (snapshot.matches({ questions: "open" })) return "needs-evidence";
|
|
184
|
+
if (snapshot.hasTag("reroute-required")) return "needs-routing";
|
|
185
|
+
if (snapshot.hasTag("verification-required")) return "needs-verification";
|
|
186
|
+
return "idle";
|
|
164
187
|
}
|
|
165
188
|
|
|
166
189
|
function isObject(value: unknown): value is Record<string, unknown> {
|
|
167
|
-
|
|
190
|
+
return Boolean(value) && typeof value === "object";
|
|
168
191
|
}
|
|
169
192
|
|
|
170
193
|
function isStringArray(value: unknown): value is string[] {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
function isMode(value: unknown): value is DeveloperMode {
|
|
175
|
-
return value === "off" || value === "on" || value === "strict";
|
|
194
|
+
return (
|
|
195
|
+
Array.isArray(value) && value.every((item) => typeof item === "string")
|
|
196
|
+
);
|
|
176
197
|
}
|
|
177
198
|
|
|
178
199
|
function isJudgmentStatus(value: unknown): value is JudgmentStatus {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
200
|
+
return (
|
|
201
|
+
value === "resolved" ||
|
|
202
|
+
value === "needs-evidence" ||
|
|
203
|
+
value === "not-applicable" ||
|
|
204
|
+
value === "blocked"
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function isImplementationProfile(
|
|
209
|
+
value: unknown,
|
|
210
|
+
): value is ImplementationProfile {
|
|
211
|
+
return value === "ordinary" || value === "behavior-preserving-structure";
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function parseImplementationStep(
|
|
215
|
+
value: unknown,
|
|
216
|
+
): ImplementationStepContract | undefined {
|
|
217
|
+
if (!isObject(value)) return undefined;
|
|
218
|
+
if (
|
|
219
|
+
typeof value.movement !== "string" ||
|
|
220
|
+
typeof value.stopCondition !== "string" ||
|
|
221
|
+
typeof value.verification !== "string"
|
|
222
|
+
) {
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
movement: value.movement,
|
|
227
|
+
stopCondition: value.stopCondition,
|
|
228
|
+
verification: value.verification,
|
|
229
|
+
};
|
|
205
230
|
}
|
|
206
231
|
|
|
207
232
|
function parseRouteAlternative(value: unknown): RouteAlternative | undefined {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
233
|
+
if (
|
|
234
|
+
!isObject(value) ||
|
|
235
|
+
typeof value.target !== "string" ||
|
|
236
|
+
typeof value.reason !== "string"
|
|
237
|
+
) {
|
|
238
|
+
return undefined;
|
|
239
|
+
}
|
|
240
|
+
return { target: value.target, reason: value.reason };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function isQuestionResolutionOwner(
|
|
244
|
+
value: unknown,
|
|
245
|
+
): value is QuestionResolutionOwner {
|
|
246
|
+
return (
|
|
247
|
+
value === "agent" ||
|
|
248
|
+
value === "user" ||
|
|
249
|
+
value === "environment" ||
|
|
250
|
+
value === "unknown"
|
|
251
|
+
);
|
|
216
252
|
}
|
|
217
253
|
|
|
218
254
|
function isQuestionGate(value: unknown): value is QuestionGate {
|
|
219
|
-
|
|
255
|
+
return (
|
|
256
|
+
value === "none" ||
|
|
257
|
+
value === "before-implementation" ||
|
|
258
|
+
value === "before-completion"
|
|
259
|
+
);
|
|
220
260
|
}
|
|
221
261
|
|
|
222
262
|
function isQuestionUpdateStatus(value: unknown): value is QuestionUpdateStatus {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
263
|
+
return (
|
|
264
|
+
value === "resolved" ||
|
|
265
|
+
value === "not-applicable" ||
|
|
266
|
+
value === "open" ||
|
|
267
|
+
value === "blocked"
|
|
268
|
+
);
|
|
226
269
|
}
|
|
227
270
|
|
|
228
271
|
const RESPONSE_IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
229
272
|
|
|
230
|
-
function requiredResponseText(
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
273
|
+
function requiredResponseText(
|
|
274
|
+
value: unknown,
|
|
275
|
+
maxChars: number,
|
|
276
|
+
): string | undefined {
|
|
277
|
+
if (typeof value !== "string") return undefined;
|
|
278
|
+
const text = value.trim();
|
|
279
|
+
return text && text.length <= maxChars ? text : undefined;
|
|
234
280
|
}
|
|
235
281
|
|
|
236
282
|
function optionalResponseText(value: unknown): string | undefined | null {
|
|
237
|
-
|
|
238
|
-
|
|
283
|
+
if (value === undefined) return undefined;
|
|
284
|
+
return requiredResponseText(value, MAX_RESPONSE_TEXT_CHARS) ?? null;
|
|
239
285
|
}
|
|
240
286
|
|
|
241
287
|
function parseChoiceResponseOption(
|
|
242
|
-
|
|
243
|
-
|
|
288
|
+
value: unknown,
|
|
289
|
+
seenValues: Set<string>,
|
|
244
290
|
): ChoiceResponseOption | undefined {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
291
|
+
if (!isObject(value)) return undefined;
|
|
292
|
+
const optionValue = requiredResponseText(
|
|
293
|
+
value.value,
|
|
294
|
+
MAX_RESPONSE_IDENTIFIER_CHARS,
|
|
295
|
+
);
|
|
296
|
+
const label = requiredResponseText(value.label, MAX_RESPONSE_TEXT_CHARS);
|
|
297
|
+
const description = optionalResponseText(value.description);
|
|
298
|
+
const detailPrompt = optionalResponseText(value.detailPrompt);
|
|
299
|
+
if (!optionValue || !RESPONSE_IDENTIFIER.test(optionValue)) return undefined;
|
|
300
|
+
if (seenValues.has(optionValue) || !label) return undefined;
|
|
301
|
+
if (description === null || detailPrompt === null) return undefined;
|
|
302
|
+
seenValues.add(optionValue);
|
|
303
|
+
return { value: optionValue, label, description, detailPrompt };
|
|
255
304
|
}
|
|
256
305
|
|
|
257
306
|
function parseChoiceResponseField(
|
|
258
|
-
|
|
259
|
-
|
|
307
|
+
value: unknown,
|
|
308
|
+
seenIds: Set<string>,
|
|
260
309
|
): ChoiceResponseField | undefined {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
310
|
+
if (!isObject(value) || !Array.isArray(value.options)) return undefined;
|
|
311
|
+
const id = requiredResponseText(value.id, MAX_RESPONSE_IDENTIFIER_CHARS);
|
|
312
|
+
const prompt = requiredResponseText(value.prompt, MAX_RESPONSE_TEXT_CHARS);
|
|
313
|
+
const description = optionalResponseText(value.description);
|
|
314
|
+
if (!id || !RESPONSE_IDENTIFIER.test(id)) return undefined;
|
|
315
|
+
if (seenIds.has(id) || !prompt || description === null) return undefined;
|
|
316
|
+
if (value.options.length < 2 || value.options.length > MAX_RESPONSE_OPTIONS)
|
|
317
|
+
return undefined;
|
|
318
|
+
|
|
319
|
+
seenIds.add(id);
|
|
320
|
+
const seenValues = new Set<string>();
|
|
321
|
+
const options: ChoiceResponseOption[] = [];
|
|
322
|
+
for (const rawOption of value.options) {
|
|
323
|
+
const option = parseChoiceResponseOption(rawOption, seenValues);
|
|
324
|
+
if (!option) return undefined;
|
|
325
|
+
options.push(option);
|
|
326
|
+
}
|
|
327
|
+
return { id, prompt, description, options };
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export function parseChoiceResponseSpec(
|
|
331
|
+
value: unknown,
|
|
332
|
+
): ChoiceResponseSpec | undefined {
|
|
333
|
+
if (
|
|
334
|
+
!isObject(value) ||
|
|
335
|
+
value.kind !== "choice-form" ||
|
|
336
|
+
!Array.isArray(value.fields)
|
|
337
|
+
) {
|
|
338
|
+
return undefined;
|
|
339
|
+
}
|
|
340
|
+
if (value.fields.length === 0 || value.fields.length > MAX_RESPONSE_FIELDS) {
|
|
341
|
+
return undefined;
|
|
342
|
+
}
|
|
343
|
+
const seenIds = new Set<string>();
|
|
344
|
+
const fields: ChoiceResponseField[] = [];
|
|
345
|
+
for (const rawField of value.fields) {
|
|
346
|
+
const field = parseChoiceResponseField(rawField, seenIds);
|
|
347
|
+
if (!field) return undefined;
|
|
348
|
+
fields.push(field);
|
|
349
|
+
}
|
|
350
|
+
return { kind: "choice-form", fields };
|
|
295
351
|
}
|
|
296
352
|
|
|
297
353
|
function parsePendingQuestion(value: unknown): PendingQuestion | undefined {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
354
|
+
if (!isObject(value)) return undefined;
|
|
355
|
+
if (
|
|
356
|
+
typeof value.id !== "string" ||
|
|
357
|
+
typeof value.question !== "string" ||
|
|
358
|
+
(value.status !== "open" &&
|
|
359
|
+
value.status !== "blocked" &&
|
|
360
|
+
value.status !== "needs-evidence") ||
|
|
361
|
+
typeof value.sourceRouteId !== "string"
|
|
362
|
+
) {
|
|
363
|
+
return undefined;
|
|
364
|
+
}
|
|
365
|
+
const legacyBlocked = value.status === "blocked";
|
|
366
|
+
let resolutionOwner: QuestionResolutionOwner = legacyBlocked
|
|
367
|
+
? "unknown"
|
|
368
|
+
: "agent";
|
|
369
|
+
if (isQuestionResolutionOwner(value.resolutionOwner))
|
|
370
|
+
resolutionOwner = value.resolutionOwner;
|
|
371
|
+
let gate: QuestionGate = legacyBlocked ? "before-completion" : "none";
|
|
372
|
+
if (isQuestionGate(value.gate)) gate = value.gate;
|
|
373
|
+
const resolutionCriteria =
|
|
374
|
+
typeof value.resolutionCriteria === "string"
|
|
375
|
+
? value.resolutionCriteria
|
|
376
|
+
: `Obtain evidence that settles: ${value.question}`;
|
|
377
|
+
const context =
|
|
378
|
+
typeof value.context === "string"
|
|
379
|
+
? value.context.trim() || undefined
|
|
380
|
+
: undefined;
|
|
381
|
+
const responseSpec =
|
|
382
|
+
resolutionOwner === "user"
|
|
383
|
+
? parseChoiceResponseSpec(value.responseSpec)
|
|
384
|
+
: undefined;
|
|
385
|
+
return {
|
|
386
|
+
id: value.id,
|
|
387
|
+
question: value.question,
|
|
388
|
+
context,
|
|
389
|
+
responseSpec,
|
|
390
|
+
status: legacyBlocked ? "blocked" : "open",
|
|
391
|
+
resolutionOwner,
|
|
392
|
+
gate,
|
|
393
|
+
resolutionCriteria,
|
|
394
|
+
sourceRouteId: value.sourceRouteId,
|
|
395
|
+
};
|
|
330
396
|
}
|
|
331
397
|
|
|
332
398
|
function parseQuestionUpdate(value: unknown): QuestionUpdate | undefined {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
export function normalizeDeveloperEvent(
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
if (openedQuestions.some((question) => !question)) return undefined;
|
|
452
|
-
if (value.questionUpdates !== undefined && !Array.isArray(value.questionUpdates))
|
|
453
|
-
return undefined;
|
|
454
|
-
const questionUpdates = Array.isArray(value.questionUpdates)
|
|
455
|
-
? value.questionUpdates.map(parseQuestionUpdate)
|
|
456
|
-
: [];
|
|
457
|
-
if (questionUpdates.some((update) => !update)) return undefined;
|
|
458
|
-
return {
|
|
459
|
-
protocol: PROTOCOL,
|
|
460
|
-
kind: "judgment",
|
|
461
|
-
routeId: value.routeId,
|
|
462
|
-
question: value.question,
|
|
463
|
-
owner: value.owner,
|
|
464
|
-
status: value.status,
|
|
465
|
-
result: value.result,
|
|
466
|
-
basis: value.basis,
|
|
467
|
-
openedQuestions: openedQuestions as PendingQuestion[],
|
|
468
|
-
questionUpdates: questionUpdates as QuestionUpdate[],
|
|
469
|
-
artifacts: value.artifacts,
|
|
470
|
-
changedArtifacts: typeof value.changedArtifacts === "boolean" ? value.changedArtifacts : false,
|
|
471
|
-
};
|
|
399
|
+
if (
|
|
400
|
+
!isObject(value) ||
|
|
401
|
+
typeof value.questionId !== "string" ||
|
|
402
|
+
!isQuestionUpdateStatus(value.status) ||
|
|
403
|
+
typeof value.result !== "string" ||
|
|
404
|
+
!isStringArray(value.basis)
|
|
405
|
+
) {
|
|
406
|
+
return undefined;
|
|
407
|
+
}
|
|
408
|
+
return {
|
|
409
|
+
questionId: value.questionId,
|
|
410
|
+
status: value.status,
|
|
411
|
+
result: value.result,
|
|
412
|
+
basis: value.basis,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
export function normalizeDeveloperEvent(
|
|
417
|
+
value: unknown,
|
|
418
|
+
): DeveloperEvent | undefined {
|
|
419
|
+
if (!isObject(value) || value.protocol !== PROTOCOL) return undefined;
|
|
420
|
+
|
|
421
|
+
if (value.kind === "activation") {
|
|
422
|
+
if (typeof value.enabled !== "boolean") return undefined;
|
|
423
|
+
return { protocol: PROTOCOL, kind: "activation", enabled: value.enabled };
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (value.kind === "focus") {
|
|
427
|
+
if (typeof value.questionId !== "string") return undefined;
|
|
428
|
+
return { protocol: PROTOCOL, kind: "focus", questionId: value.questionId };
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (value.kind === "route") {
|
|
432
|
+
if (
|
|
433
|
+
typeof value.routeId !== "string" ||
|
|
434
|
+
typeof value.question !== "string" ||
|
|
435
|
+
typeof value.target !== "string" ||
|
|
436
|
+
typeof value.reason !== "string" ||
|
|
437
|
+
!isStringArray(value.knownEvidence) ||
|
|
438
|
+
(value.consideredAlternatives !== undefined &&
|
|
439
|
+
!Array.isArray(value.consideredAlternatives)) ||
|
|
440
|
+
(value.targetQuestionId !== undefined &&
|
|
441
|
+
typeof value.targetQuestionId !== "string") ||
|
|
442
|
+
(value.methodLocation !== undefined &&
|
|
443
|
+
typeof value.methodLocation !== "string") ||
|
|
444
|
+
(value.executionProfile !== undefined &&
|
|
445
|
+
!isImplementationProfile(value.executionProfile)) ||
|
|
446
|
+
(value.implementationStep !== undefined &&
|
|
447
|
+
!parseImplementationStep(value.implementationStep))
|
|
448
|
+
) {
|
|
449
|
+
return undefined;
|
|
450
|
+
}
|
|
451
|
+
const consideredAlternatives = Array.isArray(value.consideredAlternatives)
|
|
452
|
+
? value.consideredAlternatives.map(parseRouteAlternative)
|
|
453
|
+
: [];
|
|
454
|
+
if (consideredAlternatives.some((alternative) => !alternative))
|
|
455
|
+
return undefined;
|
|
456
|
+
return {
|
|
457
|
+
protocol: PROTOCOL,
|
|
458
|
+
kind: "route",
|
|
459
|
+
routeId: value.routeId,
|
|
460
|
+
question: value.question,
|
|
461
|
+
target: value.target,
|
|
462
|
+
reason: value.reason,
|
|
463
|
+
knownEvidence: value.knownEvidence,
|
|
464
|
+
consideredAlternatives: consideredAlternatives as RouteAlternative[],
|
|
465
|
+
targetQuestionId: value.targetQuestionId,
|
|
466
|
+
methodLocation: value.methodLocation,
|
|
467
|
+
executionProfile: value.executionProfile,
|
|
468
|
+
implementationStep:
|
|
469
|
+
value.implementationStep === undefined
|
|
470
|
+
? undefined
|
|
471
|
+
: parseImplementationStep(value.implementationStep),
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (value.kind !== "judgment") return undefined;
|
|
476
|
+
if (
|
|
477
|
+
typeof value.routeId !== "string" ||
|
|
478
|
+
typeof value.question !== "string" ||
|
|
479
|
+
typeof value.target !== "string" ||
|
|
480
|
+
!isJudgmentStatus(value.status) ||
|
|
481
|
+
typeof value.result !== "string" ||
|
|
482
|
+
!isStringArray(value.basis) ||
|
|
483
|
+
!isStringArray(value.artifacts) ||
|
|
484
|
+
!Array.isArray(value.openedQuestions)
|
|
485
|
+
) {
|
|
486
|
+
return undefined;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const openedQuestions = value.openedQuestions.map(parsePendingQuestion);
|
|
490
|
+
if (openedQuestions.some((question) => !question)) return undefined;
|
|
491
|
+
if (
|
|
492
|
+
value.questionUpdates !== undefined &&
|
|
493
|
+
!Array.isArray(value.questionUpdates)
|
|
494
|
+
)
|
|
495
|
+
return undefined;
|
|
496
|
+
const questionUpdates = Array.isArray(value.questionUpdates)
|
|
497
|
+
? value.questionUpdates.map(parseQuestionUpdate)
|
|
498
|
+
: [];
|
|
499
|
+
if (questionUpdates.some((update) => !update)) return undefined;
|
|
500
|
+
return {
|
|
501
|
+
protocol: PROTOCOL,
|
|
502
|
+
kind: "judgment",
|
|
503
|
+
routeId: value.routeId,
|
|
504
|
+
question: value.question,
|
|
505
|
+
target: value.target,
|
|
506
|
+
status: value.status,
|
|
507
|
+
result: value.result,
|
|
508
|
+
basis: value.basis,
|
|
509
|
+
openedQuestions: openedQuestions as PendingQuestion[],
|
|
510
|
+
questionUpdates: questionUpdates as QuestionUpdate[],
|
|
511
|
+
artifacts: value.artifacts,
|
|
512
|
+
changedArtifacts:
|
|
513
|
+
typeof value.changedArtifacts === "boolean"
|
|
514
|
+
? value.changedArtifacts
|
|
515
|
+
: false,
|
|
516
|
+
};
|
|
472
517
|
}
|
|
473
518
|
|
|
474
519
|
interface BranchEntryLike {
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
const DEVELOPER_TOOL_NAMES = new Set<string>([
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
}, initialState());
|
|
520
|
+
type: string;
|
|
521
|
+
customType?: string;
|
|
522
|
+
data?: unknown;
|
|
523
|
+
message?: { role?: string; toolName?: string; details?: unknown };
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const DEVELOPER_TOOL_NAMES = new Set<string>([ROUTE_TOOL, JUDGMENT_TOOL]);
|
|
527
|
+
|
|
528
|
+
export function eventFromBranchEntry(
|
|
529
|
+
entry: BranchEntryLike,
|
|
530
|
+
): DeveloperEvent | undefined {
|
|
531
|
+
if (
|
|
532
|
+
entry.type === "custom" &&
|
|
533
|
+
(entry.customType === ACTIVATION_ENTRY || entry.customType === FOCUS_ENTRY)
|
|
534
|
+
) {
|
|
535
|
+
return normalizeDeveloperEvent(entry.data);
|
|
536
|
+
}
|
|
537
|
+
if (
|
|
538
|
+
entry.type === "message" &&
|
|
539
|
+
entry.message?.role === "toolResult" &&
|
|
540
|
+
entry.message.toolName &&
|
|
541
|
+
DEVELOPER_TOOL_NAMES.has(entry.message.toolName)
|
|
542
|
+
) {
|
|
543
|
+
return normalizeDeveloperEvent(entry.message.details);
|
|
544
|
+
}
|
|
545
|
+
return undefined;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
export function reconstructState(
|
|
549
|
+
entries: ReadonlyArray<BranchEntryLike>,
|
|
550
|
+
): DeveloperState {
|
|
551
|
+
return entries.reduce((state, entry) => {
|
|
552
|
+
const event = eventFromBranchEntry(entry);
|
|
553
|
+
return event ? applyDeveloperEvent(state, event) : state;
|
|
554
|
+
}, initialState());
|
|
511
555
|
}
|