kiokuko-dsh 0.2.28 → 0.2.30
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/dist/dsh/clarification-context.d.ts.map +1 -1
- package/dist/dsh/clarification-context.js +24 -7
- package/dist/dsh/clarification-context.js.map +1 -1
- package/dist/dsh/coding-workflow/host.d.ts +1 -1
- package/dist/dsh/coding-workflow/host.d.ts.map +1 -1
- package/dist/dsh/coding-workflow/host.js +226 -83
- package/dist/dsh/coding-workflow/host.js.map +1 -1
- package/dist/dsh/core/host.d.ts.map +1 -1
- package/dist/dsh/core/host.js +1026 -315
- package/dist/dsh/core/host.js.map +1 -1
- package/dist/dsh/core/tasks.d.ts.map +1 -1
- package/dist/dsh/core/tasks.js +9 -2
- package/dist/dsh/core/tasks.js.map +1 -1
- package/dist/dsh/decisions/intent-assessment.d.ts +1 -0
- package/dist/dsh/decisions/intent-assessment.d.ts.map +1 -1
- package/dist/dsh/decisions/intent-assessment.js +100 -18
- package/dist/dsh/decisions/intent-assessment.js.map +1 -1
- package/dist/dsh/decisions/request-intent.d.ts.map +1 -1
- package/dist/dsh/decisions/request-intent.js +405 -138
- package/dist/dsh/decisions/request-intent.js.map +1 -1
- package/dist/dsh/host-adapter/admission.d.ts +2 -1
- package/dist/dsh/host-adapter/admission.d.ts.map +1 -1
- package/dist/dsh/host-adapter/admission.js +877 -312
- package/dist/dsh/host-adapter/admission.js.map +1 -1
- package/dist/dsh/host-adapter.d.ts.map +1 -1
- package/dist/dsh/host-adapter.js +1 -0
- package/dist/dsh/host-adapter.js.map +1 -1
- package/dist/dsh/on-demand-intake.d.ts +1 -0
- package/dist/dsh/on-demand-intake.d.ts.map +1 -1
- package/dist/dsh/on-demand-intake.js +1120 -316
- package/dist/dsh/on-demand-intake.js.map +1 -1
- package/dist/dsh/request-proposals.d.ts +8 -0
- package/dist/dsh/request-proposals.d.ts.map +1 -0
- package/dist/dsh/request-proposals.js +67 -0
- package/dist/dsh/request-proposals.js.map +1 -0
- package/dist/dsh/skill-prompts.json +1 -1
- package/dist/locale/en.json +1 -1
- package/dist/locale/ja.json +1 -1
- package/dist/locale/ko.json +1 -1
- package/dist/locale/zh-CN.json +1 -1
- package/package.json +2 -2
- package/skills/coding-ideal-routine-skill/SKILL.md +1 -1
|
@@ -1,24 +1,57 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { canonicalContentHash } from '../../serialization/validate.js';
|
|
3
3
|
import { DecisionError } from './contracts.js';
|
|
4
|
-
import { TaskSnapshotSchema, TASK_OPERATIONS, validateTaskOperation } from '../task-state.js';
|
|
5
|
-
import { correlateRequestIntent, requestIntentDiagnosticId } from './diagnostic-correlation.js';
|
|
4
|
+
import { TaskSnapshotSchema, TASK_OPERATIONS, validateTaskOperation, } from '../task-state.js';
|
|
5
|
+
import { correlateRequestIntent, requestIntentDiagnosticId, } from './diagnostic-correlation.js';
|
|
6
6
|
export const REQUEST_INTENT_POLICY_VERSION = 'request-intent-task-state-v3';
|
|
7
7
|
export const MAX_REQUEST_INTENT_BYTES = 32 * 1024;
|
|
8
|
-
export const RequestRelationship = z.enum([
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
export const RequestRelationship = z.enum([
|
|
9
|
+
'new',
|
|
10
|
+
'same_scope_continuation',
|
|
11
|
+
'scope_change',
|
|
12
|
+
'unknown',
|
|
13
|
+
]);
|
|
14
|
+
export const RequestAction = z.enum([
|
|
15
|
+
'answer',
|
|
16
|
+
'research',
|
|
17
|
+
'verify',
|
|
18
|
+
'change',
|
|
19
|
+
'operate',
|
|
20
|
+
'unknown',
|
|
21
|
+
]);
|
|
22
|
+
export const RequestChangeScope = z.enum([
|
|
23
|
+
'implementation',
|
|
24
|
+
'document',
|
|
25
|
+
'unknown',
|
|
26
|
+
]);
|
|
27
|
+
const safeId = z
|
|
28
|
+
.string()
|
|
29
|
+
.min(1)
|
|
30
|
+
.max(256)
|
|
31
|
+
.refine((value) => !/[\p{Cc}\p{Cf}]/u.test(value));
|
|
12
32
|
const boundedText = z.string().min(1).max(8192);
|
|
13
|
-
const messageReference = z
|
|
14
|
-
|
|
33
|
+
const messageReference = z
|
|
34
|
+
.object({ id: safeId, digest: z.string().regex(/^[a-f0-9]{64}$/u) })
|
|
35
|
+
.strict();
|
|
36
|
+
const proposalReference = z
|
|
37
|
+
.object({
|
|
15
38
|
id: safeId,
|
|
16
39
|
revision: z.number().int().min(0).nullable(),
|
|
17
40
|
digest: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
18
41
|
text: boundedText,
|
|
19
|
-
field: z
|
|
20
|
-
|
|
21
|
-
|
|
42
|
+
field: z
|
|
43
|
+
.enum([
|
|
44
|
+
'readPaths',
|
|
45
|
+
'writePaths',
|
|
46
|
+
'excludedPaths',
|
|
47
|
+
'constraints',
|
|
48
|
+
'completion',
|
|
49
|
+
])
|
|
50
|
+
.optional(),
|
|
51
|
+
})
|
|
52
|
+
.strict();
|
|
53
|
+
const activeGoal = z
|
|
54
|
+
.object({
|
|
22
55
|
runId: safeId,
|
|
23
56
|
workflowRevision: z.number().int().min(1).optional(),
|
|
24
57
|
goalId: safeId.optional(),
|
|
@@ -28,8 +61,10 @@ const activeGoal = z.object({
|
|
|
28
61
|
target: boundedText.optional(),
|
|
29
62
|
outOfScope: z.array(boundedText).max(32).default([]),
|
|
30
63
|
constraints: z.array(boundedText).max(32).default([]),
|
|
31
|
-
})
|
|
32
|
-
|
|
64
|
+
})
|
|
65
|
+
.strict();
|
|
66
|
+
export const RequestIntentContextSchema = z
|
|
67
|
+
.object({
|
|
33
68
|
/** Exact current user request. No transcript or other session is accepted. */
|
|
34
69
|
task: z.string().min(1).max(16_384),
|
|
35
70
|
messageRefs: z.array(messageReference).min(1).max(16),
|
|
@@ -37,27 +72,48 @@ export const RequestIntentContextSchema = z.object({
|
|
|
37
72
|
/** SQLite task identity and revision, independent of an implementation-only Goal. */
|
|
38
73
|
activeTask: TaskSnapshotSchema.optional(),
|
|
39
74
|
proposals: z.array(proposalReference).max(8).default([]),
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
|
|
75
|
+
})
|
|
76
|
+
.strict()
|
|
77
|
+
.superRefine((value, context) => {
|
|
78
|
+
if (new Set(value.messageRefs.map((item) => item.id)).size !==
|
|
79
|
+
value.messageRefs.length) {
|
|
80
|
+
context.addIssue({
|
|
81
|
+
code: 'custom',
|
|
82
|
+
path: ['messageRefs'],
|
|
83
|
+
message: 'Message references must be unique',
|
|
84
|
+
});
|
|
43
85
|
}
|
|
44
|
-
if (new Set(value.proposals.map(item => item.id)).size !==
|
|
45
|
-
|
|
86
|
+
if (new Set(value.proposals.map((item) => item.id)).size !==
|
|
87
|
+
value.proposals.length) {
|
|
88
|
+
context.addIssue({
|
|
89
|
+
code: 'custom',
|
|
90
|
+
path: ['proposals'],
|
|
91
|
+
message: 'Proposal references must be unique',
|
|
92
|
+
});
|
|
46
93
|
}
|
|
47
|
-
if (Buffer.byteLength(JSON.stringify(value), 'utf8') >
|
|
48
|
-
|
|
94
|
+
if (Buffer.byteLength(JSON.stringify(value), 'utf8') >
|
|
95
|
+
MAX_REQUEST_INTENT_BYTES) {
|
|
96
|
+
context.addIssue({
|
|
97
|
+
code: 'custom',
|
|
98
|
+
message: 'Request intent context exceeds its byte limit',
|
|
99
|
+
});
|
|
49
100
|
}
|
|
50
101
|
});
|
|
51
102
|
export { requestIntentMessageRefs } from './message-refs.js';
|
|
52
103
|
export function createRequestIntentContext(input) {
|
|
53
104
|
// Request-scoped diagnostics are not new task authority and must not invalidate their own retry context.
|
|
54
105
|
const { unresolved: _unresolved, ...activeTask } = input.activeTask ?? {};
|
|
55
|
-
const parsed = RequestIntentContextSchema.safeParse({
|
|
106
|
+
const parsed = RequestIntentContextSchema.safeParse({
|
|
107
|
+
task: input.task,
|
|
108
|
+
messageRefs: input.messageRefs,
|
|
56
109
|
...(input.activeGoal === undefined ? {} : { activeGoal: input.activeGoal }),
|
|
57
|
-
...(input.activeTask === undefined ? {} : { activeTask }),
|
|
110
|
+
...(input.activeTask === undefined ? {} : { activeTask }),
|
|
111
|
+
proposals: input.proposals ?? [],
|
|
112
|
+
});
|
|
58
113
|
return parsed.success ? parsed.data : undefined;
|
|
59
114
|
}
|
|
60
|
-
export const RequestIntentSubmissionSchema = z
|
|
115
|
+
export const RequestIntentSubmissionSchema = z
|
|
116
|
+
.object({
|
|
61
117
|
relation: RequestRelationship,
|
|
62
118
|
action: RequestAction,
|
|
63
119
|
/** Missing legacy values normalize to unknown and can never authorize a change. */
|
|
@@ -66,29 +122,74 @@ export const RequestIntentSubmissionSchema = z.object({
|
|
|
66
122
|
taskOperation: z.enum(TASK_OPERATIONS).optional(),
|
|
67
123
|
/** Exact opaque references supplied in the current host context. */
|
|
68
124
|
evidenceRefs: z.array(safeId).min(1).max(32),
|
|
69
|
-
})
|
|
125
|
+
})
|
|
126
|
+
.strict()
|
|
127
|
+
.superRefine((value, context) => {
|
|
70
128
|
if (new Set(value.evidenceRefs).size !== value.evidenceRefs.length) {
|
|
71
|
-
context.addIssue({
|
|
129
|
+
context.addIssue({
|
|
130
|
+
code: 'custom',
|
|
131
|
+
path: ['evidenceRefs'],
|
|
132
|
+
message: 'Evidence references must be unique',
|
|
133
|
+
});
|
|
72
134
|
}
|
|
73
135
|
});
|
|
74
136
|
const relationshipChoices = [
|
|
75
|
-
{
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
137
|
+
{
|
|
138
|
+
id: 'new',
|
|
139
|
+
description: 'A new task, not a continuation or revision of the active Goal or listed proposals.',
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
id: 'same_scope_continuation',
|
|
143
|
+
description: 'Continue, inspect, verify, or finish the current source-bound conversation or active task without changing its target, exclusions, or requirements. A formal Goal is optional.',
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: 'scope_change',
|
|
147
|
+
description: 'Change the active Goal, target, exclusions, requirements, or adopt proposals that expand or alter its scope.',
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
id: 'unknown',
|
|
151
|
+
description: 'The current message does not establish its relationship to the active Goal or proposals.',
|
|
152
|
+
},
|
|
79
153
|
];
|
|
80
154
|
const actionChoices = [
|
|
81
|
-
{
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
{
|
|
86
|
-
|
|
155
|
+
{
|
|
156
|
+
id: 'answer',
|
|
157
|
+
description: 'Answer, explain, or discuss; no tool-backed work is requested.',
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
id: 'research',
|
|
161
|
+
description: 'Inspect sources or gather information without changing project state.',
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
id: 'verify',
|
|
165
|
+
description: 'Inspect behavior, run or review checks, or establish whether something works.',
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
id: 'change',
|
|
169
|
+
description: 'Modify code, files, configuration, or another project artifact.',
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
id: 'operate',
|
|
173
|
+
description: 'Perform deployment, restart, installation, or other system/service operation.',
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
id: 'unknown',
|
|
177
|
+
description: 'The requested action or target is ambiguous, or not supported by the current evidence. Multiple compatible requested actions alone are not ambiguity.',
|
|
178
|
+
},
|
|
87
179
|
];
|
|
88
180
|
const changeScopeChoices = [
|
|
89
|
-
{
|
|
90
|
-
|
|
91
|
-
|
|
181
|
+
{
|
|
182
|
+
id: 'implementation',
|
|
183
|
+
description: 'The requested artifact change affects code, tests, runtime behavior, or configuration behavior. If documentation is changed together with implementation, choose implementation.',
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
id: 'document',
|
|
187
|
+
description: 'The requested change is prose-only documentation and does not change code, tests, or behavior configuration.',
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
id: 'unknown',
|
|
191
|
+
description: 'The requested artifact scope cannot be determined from the current request and supplied Goal/proposal references.',
|
|
192
|
+
},
|
|
92
193
|
];
|
|
93
194
|
/** Finite choices preserve both the action and the proposals it refers to. */
|
|
94
195
|
export function buildRequestIntentBatch(raw) {
|
|
@@ -106,23 +207,71 @@ export function buildRequestIntentBatch(raw) {
|
|
|
106
207
|
boundary: 'Use only this exact current request and the supplied active Goal/proposal candidates. Never use another chat, infer a free-text target, or grant execution permission.',
|
|
107
208
|
},
|
|
108
209
|
questions: [
|
|
109
|
-
{
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
210
|
+
{
|
|
211
|
+
id: 'request-relation',
|
|
212
|
+
instructions: 'Classify the relationship of the current user message to the supplied active Goal and proposals. Respect negation, quotation, actor, and omitted references. An exact continuation phrase is not required. If the intended reference or scope is not established, choose unknown.',
|
|
213
|
+
choices: [...relationshipChoices],
|
|
214
|
+
abstainId: 'unknown',
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
id: 'request-action',
|
|
218
|
+
instructions: 'Classify the action the user requests now, not a quoted example or an action they forbid. “See whether it works” is verify/research, not a file edit. When explanation or inspection accompanies an explicit edit request, choose change and preserve every requested action and prohibition. Do not turn a verification-only request into an edit. Classification grants no permission. If the action or target is unclear, choose unknown.',
|
|
219
|
+
choices: [...actionChoices],
|
|
220
|
+
abstainId: 'unknown',
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
id: 'request-change-scope',
|
|
224
|
+
instructions: 'For action=change, classify the exact current requested artifact scope using only the current message and supplied Goal/proposal references. Code, tests, runtime behavior, or behavior configuration is implementation; a request that mixes implementation with documentation is implementation. Prose-only document edits are document. Do not infer scope from an advisory task label, wording style, or whether the message happens to say “write”. For other actions choose unknown.',
|
|
225
|
+
choices: [...changeScopeChoices],
|
|
226
|
+
abstainId: 'unknown',
|
|
227
|
+
},
|
|
228
|
+
...(parsed.data.activeTask
|
|
229
|
+
? [
|
|
230
|
+
{
|
|
231
|
+
id: 'request-task-operation',
|
|
232
|
+
instructions: 'Propose only a structured state operation clearly requested by the current human message for the exact SQLite task. Do not mutate state, fabricate completion or stop evidence, or infer approval from a keyword. Choose none when no state transition is requested; choose unknown when ambiguous. Terminal resume requires an explicit user operation.',
|
|
233
|
+
choices: [
|
|
234
|
+
{
|
|
235
|
+
id: 'none',
|
|
236
|
+
description: 'No task state operation is requested.',
|
|
237
|
+
},
|
|
238
|
+
...parsed.data.activeTask.allowedOps.map((operation) => ({
|
|
239
|
+
id: operation,
|
|
240
|
+
description: `Propose ${operation} for this exact task revision; host validation and evidence still apply.`,
|
|
241
|
+
})),
|
|
242
|
+
{
|
|
243
|
+
id: 'unknown',
|
|
244
|
+
description: 'The intended operation or target is uncertain.',
|
|
245
|
+
},
|
|
246
|
+
],
|
|
247
|
+
abstainId: 'unknown',
|
|
248
|
+
},
|
|
249
|
+
]
|
|
250
|
+
: []),
|
|
251
|
+
...parsed.data.proposals.map((proposal, index) => ({
|
|
252
|
+
id: `request-proposal-${index}`,
|
|
117
253
|
instructions: `Does the current user instruction adopt or refer to proposal ${JSON.stringify(proposal.id)}? Interpret omitted Japanese subjects and references such as 全部, それ, 続けて against the supplied context. Quoted or forbidden actions are not approvals.`,
|
|
118
|
-
choices: [
|
|
119
|
-
{
|
|
120
|
-
|
|
254
|
+
choices: [
|
|
255
|
+
{
|
|
256
|
+
id: 'selected',
|
|
257
|
+
description: 'The current request refers to this proposal.',
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
id: 'not_selected',
|
|
261
|
+
description: 'This proposal is not referenced by the current request.',
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
id: 'unknown',
|
|
265
|
+
description: 'The reference cannot be resolved from the supplied context.',
|
|
266
|
+
},
|
|
267
|
+
],
|
|
268
|
+
abstainId: 'unknown',
|
|
269
|
+
})),
|
|
121
270
|
],
|
|
122
271
|
};
|
|
123
272
|
}
|
|
124
273
|
export function requestIntentReferenceIds(context) {
|
|
125
|
-
const refs = context.messageRefs.map(item => `message:${canonicalContentHash(item)}`);
|
|
274
|
+
const refs = context.messageRefs.map((item) => `message:${canonicalContentHash(item)}`);
|
|
126
275
|
if (context.activeGoal) {
|
|
127
276
|
refs.push(`run:${canonicalContentHash(context.activeGoal.runId)}`);
|
|
128
277
|
if (context.activeGoal.workflowRevision !== undefined)
|
|
@@ -134,37 +283,61 @@ export function requestIntentReferenceIds(context) {
|
|
|
134
283
|
}
|
|
135
284
|
if (context.activeTask)
|
|
136
285
|
refs.push(`task:${canonicalContentHash([context.activeTask.taskId, context.activeTask.revision, context.activeTask.generation])}`);
|
|
137
|
-
refs.push(...context.proposals.map(item => `proposal:${canonicalContentHash([item.id, item.revision, item.digest])}`));
|
|
286
|
+
refs.push(...context.proposals.map((item) => `proposal:${canonicalContentHash([item.id, item.revision, item.digest])}`));
|
|
138
287
|
return refs;
|
|
139
288
|
}
|
|
140
289
|
/** Current identity is mandatory; proposal references are optional evidence of selection. */
|
|
141
290
|
export function requestIntentRequiredReferenceIds(context) {
|
|
142
|
-
return requestIntentReferenceIds(context).filter(reference => !reference.startsWith('proposal:'));
|
|
291
|
+
return requestIntentReferenceIds(context).filter((reference) => !reference.startsWith('proposal:'));
|
|
143
292
|
}
|
|
144
293
|
function evidenceFor(context, selectedProposalIds = new Set()) {
|
|
145
294
|
const active = context.activeGoal;
|
|
146
295
|
return {
|
|
147
296
|
requestDigest: canonicalContentHash(context.task),
|
|
148
|
-
messageRefs: context.messageRefs.map(item => ({ ...item })),
|
|
149
|
-
...(active
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
297
|
+
messageRefs: context.messageRefs.map((item) => ({ ...item })),
|
|
298
|
+
...(active
|
|
299
|
+
? {
|
|
300
|
+
runId: active.runId,
|
|
301
|
+
...(active.workflowRevision === undefined
|
|
302
|
+
? {}
|
|
303
|
+
: { workflowRevision: active.workflowRevision }),
|
|
304
|
+
...(active.goalRevision === undefined
|
|
305
|
+
? {}
|
|
306
|
+
: { goalRevision: active.goalRevision }),
|
|
307
|
+
...(active.scopeRevision === undefined
|
|
308
|
+
? {}
|
|
309
|
+
: { scopeRevision: active.scopeRevision }),
|
|
310
|
+
}
|
|
311
|
+
: {}),
|
|
312
|
+
...(context.activeTask
|
|
313
|
+
? {
|
|
314
|
+
taskId: context.activeTask.taskId,
|
|
315
|
+
taskRevision: context.activeTask.revision,
|
|
316
|
+
taskGeneration: context.activeTask.generation,
|
|
317
|
+
}
|
|
318
|
+
: {}),
|
|
319
|
+
proposalRefs: context.proposals
|
|
320
|
+
.filter((item) => selectedProposalIds.has(item.id))
|
|
158
321
|
.map(({ id, revision, digest }) => ({ id, revision, digest })),
|
|
159
322
|
};
|
|
160
323
|
}
|
|
161
324
|
export function unresolvedRequestIntent(context, reason, providerSent = false) {
|
|
162
|
-
return {
|
|
163
|
-
|
|
325
|
+
return {
|
|
326
|
+
relation: 'unknown',
|
|
327
|
+
action: 'unknown',
|
|
328
|
+
changeScope: 'unknown',
|
|
329
|
+
source: 'unresolved',
|
|
330
|
+
policyVersion: REQUEST_INTENT_POLICY_VERSION,
|
|
331
|
+
evidence: evidenceFor(context),
|
|
332
|
+
providerSent,
|
|
333
|
+
unresolvedReason: reason,
|
|
334
|
+
};
|
|
164
335
|
}
|
|
165
336
|
export function requestIntentIsUnresolved(value) {
|
|
166
|
-
return value === undefined ||
|
|
167
|
-
|
|
337
|
+
return (value === undefined ||
|
|
338
|
+
value.relation === 'unknown' ||
|
|
339
|
+
value.action === 'unknown' ||
|
|
340
|
+
(value.action === 'change' && value.changeScope === 'unknown'));
|
|
168
341
|
}
|
|
169
342
|
/** Validate exact current-session evidence before checking whether its Goal supports the selected relation. */
|
|
170
343
|
export function validateMainModelRequestIntent(raw, context) {
|
|
@@ -174,32 +347,55 @@ export function validateMainModelRequestIntent(raw, context) {
|
|
|
174
347
|
if (parsed.data.relation === 'unknown' || parsed.data.action === 'unknown')
|
|
175
348
|
return { ok: false, reason: 'unresolved_intent' };
|
|
176
349
|
const allowed = new Set(requestIntentReferenceIds(context));
|
|
177
|
-
if (parsed.data.evidenceRefs.some(reference => !allowed.has(reference)))
|
|
350
|
+
if (parsed.data.evidenceRefs.some((reference) => !allowed.has(reference)))
|
|
178
351
|
return { ok: false, reason: 'unknown_reference' };
|
|
179
352
|
const requiredRefs = requestIntentRequiredReferenceIds(context);
|
|
180
|
-
if (!requiredRefs.every(reference => parsed.data.evidenceRefs.includes(reference)))
|
|
353
|
+
if (!requiredRefs.every((reference) => parsed.data.evidenceRefs.includes(reference)))
|
|
181
354
|
return { ok: false, reason: 'missing_reference' };
|
|
182
|
-
if (parsed.data.relation === 'scope_change' &&
|
|
355
|
+
if (parsed.data.relation === 'scope_change' &&
|
|
356
|
+
!context.activeGoal &&
|
|
357
|
+
!context.activeTask &&
|
|
358
|
+
!context.proposals.some((item) => parsed.data.evidenceRefs.includes(`proposal:${canonicalContentHash([item.id, item.revision, item.digest])}`)))
|
|
183
359
|
return { ok: false, reason: 'no_active_goal' };
|
|
184
360
|
if (parsed.data.taskOperation) {
|
|
185
361
|
if (!context.activeTask)
|
|
186
362
|
return { ok: false, reason: 'invalid_task_operation' };
|
|
187
363
|
try {
|
|
188
|
-
validateTaskOperation(context.activeTask, {
|
|
189
|
-
|
|
190
|
-
|
|
364
|
+
validateTaskOperation(context.activeTask, {
|
|
365
|
+
operation: parsed.data.taskOperation,
|
|
366
|
+
operationId: `intent:${canonicalContentHash(context)}`,
|
|
367
|
+
expectedRevision: context.activeTask.revision,
|
|
368
|
+
expectedGeneration: context.activeTask.generation,
|
|
369
|
+
reason: 'Advisory interpretation of the current human request',
|
|
370
|
+
evidence: [],
|
|
371
|
+
explicit: false,
|
|
372
|
+
});
|
|
191
373
|
}
|
|
192
374
|
catch {
|
|
193
375
|
return { ok: false, reason: 'invalid_task_operation' };
|
|
194
376
|
}
|
|
195
377
|
}
|
|
196
|
-
const selectedProposalIds = new Set(context.proposals
|
|
197
|
-
.includes(`proposal:${canonicalContentHash([item.id, item.revision, item.digest])}`))
|
|
198
|
-
|
|
199
|
-
|
|
378
|
+
const selectedProposalIds = new Set(context.proposals
|
|
379
|
+
.filter((item) => parsed.data.evidenceRefs.includes(`proposal:${canonicalContentHash([item.id, item.revision, item.digest])}`))
|
|
380
|
+
.map((item) => item.id));
|
|
381
|
+
const changeScope = parsed.data.action === 'change'
|
|
382
|
+
? (parsed.data.changeScope ?? 'unknown')
|
|
383
|
+
: 'unknown';
|
|
384
|
+
return {
|
|
385
|
+
ok: true,
|
|
386
|
+
decision: {
|
|
387
|
+
relation: parsed.data.relation,
|
|
388
|
+
action: parsed.data.action,
|
|
389
|
+
changeScope,
|
|
200
390
|
source: 'main-model',
|
|
201
|
-
policyVersion: REQUEST_INTENT_POLICY_VERSION,
|
|
202
|
-
|
|
391
|
+
policyVersion: REQUEST_INTENT_POLICY_VERSION,
|
|
392
|
+
evidence: evidenceFor(context, selectedProposalIds),
|
|
393
|
+
providerSent: false,
|
|
394
|
+
...(parsed.data.taskOperation
|
|
395
|
+
? { taskOperation: parsed.data.taskOperation }
|
|
396
|
+
: {}),
|
|
397
|
+
},
|
|
398
|
+
};
|
|
203
399
|
}
|
|
204
400
|
/** Validate a main-model interpretation against exact refs from this request and its active session only. */
|
|
205
401
|
export function bindMainModelRequestIntent(raw, context) {
|
|
@@ -211,40 +407,57 @@ export function rebindRequestIntentDecision(raw, context) {
|
|
|
211
407
|
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
212
408
|
return undefined;
|
|
213
409
|
const value = raw;
|
|
214
|
-
if (!RequestRelationship.safeParse(value.relation).success ||
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
410
|
+
if (!RequestRelationship.safeParse(value.relation).success ||
|
|
411
|
+
!RequestAction.safeParse(value.action).success ||
|
|
412
|
+
!RequestChangeScope.safeParse(value.changeScope).success ||
|
|
413
|
+
!['decision-provider', 'main-model', 'unresolved'].includes(value.source) ||
|
|
414
|
+
value.policyVersion !== REQUEST_INTENT_POLICY_VERSION ||
|
|
415
|
+
!value.evidence ||
|
|
416
|
+
!Array.isArray(value.evidence.messageRefs) ||
|
|
417
|
+
!Array.isArray(value.evidence.proposalRefs) ||
|
|
418
|
+
value.evidence.requestDigest !== canonicalContentHash(context.task))
|
|
220
419
|
return undefined;
|
|
221
420
|
const refs = requestIntentReferenceIds(context);
|
|
222
|
-
const messagesMatch = value.evidence.messageRefs.length === context.messageRefs.length
|
|
223
|
-
|
|
224
|
-
|
|
421
|
+
const messagesMatch = value.evidence.messageRefs.length === context.messageRefs.length &&
|
|
422
|
+
context.messageRefs.every((item, index) => value.evidence.messageRefs[index]?.id === item.id &&
|
|
423
|
+
value.evidence.messageRefs[index]?.digest === item.digest);
|
|
225
424
|
const active = context.activeGoal;
|
|
226
425
|
const task = context.activeTask;
|
|
227
|
-
if (value.evidence.taskId !== task?.taskId ||
|
|
228
|
-
|
|
426
|
+
if (value.evidence.taskId !== task?.taskId ||
|
|
427
|
+
value.evidence.taskRevision !== task?.revision ||
|
|
428
|
+
value.evidence.taskGeneration !== task?.generation)
|
|
229
429
|
return undefined;
|
|
230
|
-
if (!messagesMatch ||
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
430
|
+
if (!messagesMatch ||
|
|
431
|
+
(active
|
|
432
|
+
? value.evidence.runId !== active.runId ||
|
|
433
|
+
value.evidence.workflowRevision !== active.workflowRevision ||
|
|
434
|
+
value.evidence.goalRevision !== active.goalRevision ||
|
|
435
|
+
value.evidence.scopeRevision !== active.scopeRevision
|
|
436
|
+
: value.evidence.runId !== undefined))
|
|
234
437
|
return undefined;
|
|
235
438
|
const selected = new Set();
|
|
236
439
|
for (const proposal of value.evidence.proposalRefs) {
|
|
237
440
|
if (!proposal || typeof proposal !== 'object')
|
|
238
441
|
return undefined;
|
|
239
|
-
const current = context.proposals.find(item => item.id === proposal.id &&
|
|
240
|
-
|
|
442
|
+
const current = context.proposals.find((item) => item.id === proposal.id &&
|
|
443
|
+
item.revision === proposal.revision &&
|
|
444
|
+
item.digest === proposal.digest);
|
|
445
|
+
if (!current ||
|
|
446
|
+
!refs.includes(`proposal:${canonicalContentHash([proposal.id, proposal.revision, proposal.digest])}`))
|
|
241
447
|
return undefined;
|
|
242
448
|
selected.add(proposal.id);
|
|
243
449
|
}
|
|
244
450
|
if (value.relation !== 'unknown' && value.action !== 'unknown') {
|
|
245
|
-
const validated = validateMainModelRequestIntent({
|
|
246
|
-
|
|
247
|
-
|
|
451
|
+
const validated = validateMainModelRequestIntent({
|
|
452
|
+
relation: value.relation,
|
|
453
|
+
action: value.action,
|
|
454
|
+
changeScope: value.changeScope,
|
|
455
|
+
...(value.taskOperation ? { taskOperation: value.taskOperation } : {}),
|
|
456
|
+
evidenceRefs: refs.filter((reference) => !reference.startsWith('proposal:') ||
|
|
457
|
+
context.proposals.some((proposal) => selected.has(proposal.id) &&
|
|
458
|
+
reference ===
|
|
459
|
+
`proposal:${canonicalContentHash([proposal.id, proposal.revision, proposal.digest])}`)),
|
|
460
|
+
}, context);
|
|
248
461
|
if (!validated.ok)
|
|
249
462
|
return undefined;
|
|
250
463
|
}
|
|
@@ -257,19 +470,31 @@ export function rebindRequestIntentDecision(raw, context) {
|
|
|
257
470
|
/** A selected action can advise intake type; it never supplies a target or execution permission. */
|
|
258
471
|
export function requestIntentTaskType(decision, fallback) {
|
|
259
472
|
switch (decision?.action) {
|
|
260
|
-
case 'answer':
|
|
261
|
-
|
|
262
|
-
case '
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
case '
|
|
267
|
-
|
|
473
|
+
case 'answer':
|
|
474
|
+
return 'chat';
|
|
475
|
+
case 'research':
|
|
476
|
+
return fallback === 'analysis' ? 'analysis' : 'research';
|
|
477
|
+
case 'verify':
|
|
478
|
+
return fallback === 'debug' ? 'debug' : 'review';
|
|
479
|
+
case 'change':
|
|
480
|
+
return decision?.changeScope === 'implementation'
|
|
481
|
+
? fallback === 'debug'
|
|
482
|
+
? 'debug'
|
|
483
|
+
: 'build'
|
|
484
|
+
: decision?.changeScope === 'document'
|
|
485
|
+
? 'writing'
|
|
486
|
+
: fallback;
|
|
487
|
+
case 'operate':
|
|
488
|
+
return 'devops';
|
|
489
|
+
default:
|
|
490
|
+
return fallback;
|
|
268
491
|
}
|
|
269
492
|
}
|
|
270
493
|
export async function classifyRequestIntent(service, requestId, rawContext, signal) {
|
|
271
494
|
let diagnosticId;
|
|
272
|
-
const result = await classifyRequestIntentAttempt(service, requestId, rawContext, signal, id => {
|
|
495
|
+
const result = await classifyRequestIntentAttempt(service, requestId, rawContext, signal, (id) => {
|
|
496
|
+
diagnosticId = id;
|
|
497
|
+
});
|
|
273
498
|
if (result && diagnosticId)
|
|
274
499
|
correlateRequestIntent(result, diagnosticId);
|
|
275
500
|
return result;
|
|
@@ -290,61 +515,103 @@ async function classifyRequestIntentAttempt(service, requestId, rawContext, sign
|
|
|
290
515
|
if (!batch)
|
|
291
516
|
return unresolvedRequestIntent(context, 'context_not_usable');
|
|
292
517
|
const outcome = await service.evaluate(requestId, batch, signal, REQUEST_INTENT_POLICY_VERSION, undefined, {
|
|
293
|
-
onProviderDispatch: () => {
|
|
294
|
-
|
|
295
|
-
|
|
518
|
+
onProviderDispatch: () => {
|
|
519
|
+
providerSent = true;
|
|
520
|
+
},
|
|
521
|
+
onEvaluation: (observation) => {
|
|
522
|
+
if (observation.decisionId)
|
|
523
|
+
observeId(observation.decisionId);
|
|
524
|
+
},
|
|
296
525
|
});
|
|
297
526
|
if (outcome.status !== 'completed')
|
|
298
527
|
return unresolvedRequestIntent(context, outcome.reason, providerSent);
|
|
299
|
-
const relation = outcome.result.answers.find(answer => answer.id === 'request-relation');
|
|
300
|
-
const action = outcome.result.answers.find(answer => answer.id === 'request-action');
|
|
301
|
-
const changeScopeAnswer = outcome.result.answers.find(answer => answer.id === 'request-change-scope');
|
|
302
|
-
const relationValue = relation?.status === 'selected' &&
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
?
|
|
528
|
+
const relation = outcome.result.answers.find((answer) => answer.id === 'request-relation');
|
|
529
|
+
const action = outcome.result.answers.find((answer) => answer.id === 'request-action');
|
|
530
|
+
const changeScopeAnswer = outcome.result.answers.find((answer) => answer.id === 'request-change-scope');
|
|
531
|
+
const relationValue = relation?.status === 'selected' &&
|
|
532
|
+
RequestRelationship.safeParse(relation.choiceId).success
|
|
533
|
+
? relation.choiceId
|
|
534
|
+
: 'unknown';
|
|
535
|
+
const actionValue = action?.status === 'selected' &&
|
|
536
|
+
RequestAction.safeParse(action.choiceId).success
|
|
537
|
+
? action.choiceId
|
|
538
|
+
: 'unknown';
|
|
539
|
+
const changeScopeValue = actionValue === 'change' &&
|
|
540
|
+
changeScopeAnswer?.status === 'selected' &&
|
|
541
|
+
RequestChangeScope.safeParse(changeScopeAnswer.choiceId).success
|
|
542
|
+
? changeScopeAnswer.choiceId
|
|
543
|
+
: 'unknown';
|
|
309
544
|
const selected = new Set();
|
|
310
545
|
let uncertainProposal = false;
|
|
311
546
|
for (const [index, proposal] of context.proposals.entries()) {
|
|
312
|
-
const answer = outcome.result.answers.find(answer => answer.id === `request-proposal-${index}`);
|
|
547
|
+
const answer = outcome.result.answers.find((answer) => answer.id === `request-proposal-${index}`);
|
|
313
548
|
if (answer?.status === 'selected' && answer.choiceId === 'selected')
|
|
314
549
|
selected.add(proposal.id);
|
|
315
|
-
else if (answer?.status !== 'selected' ||
|
|
550
|
+
else if (answer?.status !== 'selected' ||
|
|
551
|
+
answer.choiceId !== 'not_selected')
|
|
316
552
|
uncertainProposal = true;
|
|
317
553
|
}
|
|
318
|
-
const unresolved = relationValue === 'unknown' ||
|
|
554
|
+
const unresolved = relationValue === 'unknown' ||
|
|
555
|
+
actionValue === 'unknown' ||
|
|
556
|
+
(actionValue === 'change' && changeScopeValue === 'unknown') ||
|
|
557
|
+
uncertainProposal;
|
|
319
558
|
if (uncertainProposal)
|
|
320
559
|
return unresolvedRequestIntent(context, 'proposal_reference_uncertain', providerSent);
|
|
321
|
-
const taskOperationAnswer = outcome.result.answers.find(answer => answer.id === 'request-task-operation');
|
|
322
|
-
const taskOperation = taskOperationAnswer?.status === 'selected' &&
|
|
323
|
-
|
|
560
|
+
const taskOperationAnswer = outcome.result.answers.find((answer) => answer.id === 'request-task-operation');
|
|
561
|
+
const taskOperation = taskOperationAnswer?.status === 'selected' &&
|
|
562
|
+
TASK_OPERATIONS.includes(taskOperationAnswer.choiceId)
|
|
563
|
+
? taskOperationAnswer.choiceId
|
|
564
|
+
: undefined;
|
|
324
565
|
// A task operation is a separate advisory candidate, not a prerequisite for
|
|
325
566
|
// ordinary work on an already authorized task. Abstention must not erase
|
|
326
567
|
// independently resolved action/scope or fabricate a lifecycle transition.
|
|
327
568
|
// Keep the diagnostic and fallback source so the main model can resolve it;
|
|
328
569
|
// actual task state, lease, scope and native permission checks still apply.
|
|
329
|
-
const taskOperationUnresolved = !!context.activeTask &&
|
|
330
|
-
|
|
570
|
+
const taskOperationUnresolved = !!context.activeTask &&
|
|
571
|
+
(!taskOperationAnswer ||
|
|
572
|
+
taskOperationAnswer.status !== 'selected' ||
|
|
573
|
+
taskOperationAnswer.choiceId === 'unknown');
|
|
331
574
|
if (!unresolved) {
|
|
332
|
-
const refs = requestIntentReferenceIds(context).filter(reference => !reference.startsWith('proposal:')
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
575
|
+
const refs = requestIntentReferenceIds(context).filter((reference) => !reference.startsWith('proposal:') ||
|
|
576
|
+
context.proposals.some((proposal) => selected.has(proposal.id) &&
|
|
577
|
+
reference ===
|
|
578
|
+
`proposal:${canonicalContentHash([proposal.id, proposal.revision, proposal.digest])}`));
|
|
579
|
+
const bound = validateMainModelRequestIntent({
|
|
580
|
+
relation: relationValue,
|
|
581
|
+
action: actionValue,
|
|
582
|
+
changeScope: changeScopeValue,
|
|
583
|
+
...(taskOperation ? { taskOperation } : {}),
|
|
584
|
+
evidenceRefs: refs,
|
|
585
|
+
}, context);
|
|
336
586
|
if (!bound.ok)
|
|
337
587
|
return unresolvedRequestIntent(context, bound.reason, providerSent);
|
|
338
|
-
return {
|
|
339
|
-
...
|
|
588
|
+
return {
|
|
589
|
+
...bound.decision,
|
|
590
|
+
source: taskOperationUnresolved ? 'unresolved' : 'decision-provider',
|
|
591
|
+
providerSent,
|
|
592
|
+
...(taskOperationUnresolved
|
|
593
|
+
? { unresolvedReason: 'task_operation_unresolved' }
|
|
594
|
+
: {}),
|
|
595
|
+
};
|
|
340
596
|
}
|
|
341
|
-
return {
|
|
342
|
-
|
|
343
|
-
|
|
597
|
+
return {
|
|
598
|
+
relation: relationValue,
|
|
599
|
+
action: actionValue,
|
|
600
|
+
changeScope: changeScopeValue,
|
|
601
|
+
source: unresolved ? 'unresolved' : 'decision-provider',
|
|
602
|
+
policyVersion: REQUEST_INTENT_POLICY_VERSION,
|
|
603
|
+
evidence: evidenceFor(context, selected),
|
|
604
|
+
providerSent,
|
|
605
|
+
...(unresolved
|
|
606
|
+
? { unresolvedReason: 'provider_abstained_or_uncertain' }
|
|
607
|
+
: {}),
|
|
608
|
+
};
|
|
344
609
|
}
|
|
345
610
|
catch (error) {
|
|
346
611
|
if (signal.aborted)
|
|
347
|
-
throw error instanceof DecisionError
|
|
612
|
+
throw error instanceof DecisionError
|
|
613
|
+
? error
|
|
614
|
+
: new DecisionError('CANCELLED');
|
|
348
615
|
return unresolvedRequestIntent(context, error instanceof DecisionError ? error.code : 'decision_provider_failed', providerSent);
|
|
349
616
|
}
|
|
350
617
|
}
|