kiokuko-dsh 0.2.28 → 0.2.29

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 (39) hide show
  1. package/dist/dsh/coding-workflow/host.d.ts +1 -1
  2. package/dist/dsh/coding-workflow/host.d.ts.map +1 -1
  3. package/dist/dsh/coding-workflow/host.js +226 -83
  4. package/dist/dsh/coding-workflow/host.js.map +1 -1
  5. package/dist/dsh/core/host.d.ts.map +1 -1
  6. package/dist/dsh/core/host.js +1026 -315
  7. package/dist/dsh/core/host.js.map +1 -1
  8. package/dist/dsh/core/tasks.d.ts.map +1 -1
  9. package/dist/dsh/core/tasks.js +9 -2
  10. package/dist/dsh/core/tasks.js.map +1 -1
  11. package/dist/dsh/decisions/intent-assessment.d.ts +1 -0
  12. package/dist/dsh/decisions/intent-assessment.d.ts.map +1 -1
  13. package/dist/dsh/decisions/intent-assessment.js +100 -18
  14. package/dist/dsh/decisions/intent-assessment.js.map +1 -1
  15. package/dist/dsh/decisions/request-intent.d.ts.map +1 -1
  16. package/dist/dsh/decisions/request-intent.js +405 -138
  17. package/dist/dsh/decisions/request-intent.js.map +1 -1
  18. package/dist/dsh/host-adapter/admission.d.ts +2 -1
  19. package/dist/dsh/host-adapter/admission.d.ts.map +1 -1
  20. package/dist/dsh/host-adapter/admission.js +877 -312
  21. package/dist/dsh/host-adapter/admission.js.map +1 -1
  22. package/dist/dsh/host-adapter.d.ts.map +1 -1
  23. package/dist/dsh/host-adapter.js +1 -0
  24. package/dist/dsh/host-adapter.js.map +1 -1
  25. package/dist/dsh/on-demand-intake.d.ts +1 -0
  26. package/dist/dsh/on-demand-intake.d.ts.map +1 -1
  27. package/dist/dsh/on-demand-intake.js +1095 -314
  28. package/dist/dsh/on-demand-intake.js.map +1 -1
  29. package/dist/dsh/request-proposals.d.ts +8 -0
  30. package/dist/dsh/request-proposals.d.ts.map +1 -0
  31. package/dist/dsh/request-proposals.js +67 -0
  32. package/dist/dsh/request-proposals.js.map +1 -0
  33. package/dist/dsh/skill-prompts.json +1 -1
  34. package/dist/locale/en.json +1 -1
  35. package/dist/locale/ja.json +1 -1
  36. package/dist/locale/ko.json +1 -1
  37. package/dist/locale/zh-CN.json +1 -1
  38. package/package.json +1 -1
  39. package/skills/coding-ideal-routine-skill/SKILL.md +1 -1
@@ -1,9 +1,9 @@
1
1
  import { isCodingWorkflowStatus } from './coding-workflow/host.js';
2
2
  import { symbols } from '@deepseek-ai/cordis';
3
- import { requestInterpretationError, requestInterpretationRecovery } from './request-interpretation-guidance.js';
4
- import { collectClarificationContext, clarificationReply, reusableClarificationAnswer } from './clarification-context.js';
3
+ import { requestInterpretationError, requestInterpretationRecovery, } from './request-interpretation-guidance.js';
4
+ import { collectClarificationContext, clarificationReply, reusableClarificationAnswer, } from './clarification-context.js';
5
5
  import { abortable } from './boundary-worker.js';
6
- import { isLispImplementation, isLispNativeCommand, withRootLispAdmission, rememberLispConversation } from './lisp-tool-identity.js';
6
+ import { isLispImplementation, isLispNativeCommand, withRootLispAdmission, rememberLispConversation, } from './lisp-tool-identity.js';
7
7
  import { ExecutionSelectionPending } from './execution-pending.js';
8
8
  import { executionClarification } from './intake-clarification.js';
9
9
  import { randomUUID } from 'node:crypto';
@@ -11,36 +11,54 @@ import { z } from 'zod';
11
11
  import { TASK_TYPES } from '../akinator/types.js';
12
12
  import { canonicalContentHash } from '../serialization/validate.js';
13
13
  import { KIOKUKO_DSH_SOURCE_KIND } from './plugin-source.js';
14
- import { taskAllowedOperations, taskCanExecute } from './task-state.js';
15
- import { createRequestIntentContext, unresolvedRequestIntent, validateMainModelRequestIntent, requestIntentReferenceIds, requestIntentRequiredReferenceIds, requestIntentIsUnresolved, requestIntentMessageRefs, requestIntentTaskType, RequestIntentSubmissionSchema } from './decisions/request-intent.js';
16
- import { memoryOwnerKind } from './memory-owner.js';
14
+ import { taskAllowedOperations, taskCanExecute, } from './task-state.js';
15
+ import { createRequestIntentContext, unresolvedRequestIntent, validateMainModelRequestIntent, requestIntentReferenceIds, requestIntentRequiredReferenceIds, requestIntentIsUnresolved, requestIntentMessageRefs, requestIntentTaskType, RequestIntentSubmissionSchema, } from './decisions/request-intent.js';
16
+ import { memoryOwnerKind, } from './memory-owner.js';
17
17
  class RequestClarification extends Error {
18
18
  assessment;
19
- constructor(message, assessment = false) {
19
+ interpretation;
20
+ constructor(message, assessment = false, interpretation = false) {
20
21
  super(message);
21
22
  this.assessment = assessment;
23
+ this.interpretation = interpretation;
22
24
  }
23
25
  }
24
26
  export const TASK_PREPARE_TOOL = 'prepare_requested_work';
25
27
  export const ON_DEMAND_GUIDANCE = 'The original user request may be answered now without selecting a task category. Ordinary native Bash needs no memory, Goal or prepare_requested_work round trip, including when optional workflow metadata exists; native permissions and human Stop remain authoritative. Registered root Lisp tools delegate to the Lisp runtime without an optional preparation round trip; foreground processes use native Bash permissions, sandbox, approval and Stop. Explicitly prepared or protected workflow effects retain their task, Plan and coding-choice checks. Owned or explicitly enrolled work retains its authority checks. Preserve the original utterance, every requested action, negation, quotation, target uncertainty, and current-session references; never replace it with a model rewrite. Use only the supplied active Goal and proposal references from this exact native session, never another chat. Classify relation as new, same_scope_continuation, scope_change, or unknown, and action as answer, research, verify, change, operate, or unknown; do not infer either from a fixed phrase or tool name. “Check how it behaves” is verification/research even when the next tool is Bash. This interpretation grants no native permission. When the decision provider is unavailable, abstains, or returns unknown, interpret the same context in the main model; ask the user only if the action or referenced target remains unclear, and preserve active work while waiting. Native read, glob, grep, web_search and web_fetch demands can gather evidence without preparation. Do not call prepare_requested_work merely to read a plan or inspect files. prepare_requested_work accepts either the legacy taskType alone or a requestIntent with exact evidenceRefs from the supplied context. taskOperation is optional and describes a transition of an existing activeTask, not starting the requested work itself. Omit taskOperation when activeTask is null or the request does not ask for a state transition; it never accepts a rewritten task or target and never changes native permissions. Installed Skill reads need no preparation and do not activate execution. Do not prepare work merely to load a skill. Plan, Goal, cancellation, memory-owner and execution ownership still apply. If only run_code is exposed, use the exact preparation-only carrier returned by this tool contract; the host intercepts it without evaluating code.';
26
- const ACTION_TYPES = TASK_TYPES.filter(type => type !== 'chat');
27
- const NO_ACTIVE_GOAL_GUIDANCE = 'The current context has no active Goal. same_scope_continuation may refer to the exact current-session conversation without a formal Goal; it grants no execution permission. scope_change requires an active Goal or task. If the original request clearly specifies a self-contained task, use relation=new and the action it requests (verify for verification), with the exact current evidenceRefs. Otherwise clarify the intended target with the user. Preserve the original scope, constraints and native permissions.';
28
- const preparationFieldsSchema = z.object({
28
+ const ACTION_TYPES = TASK_TYPES.filter((type) => type !== 'chat');
29
+ const NO_ACTIVE_GOAL_GUIDANCE = 'The current context has no active Goal. same_scope_continuation may refer to the exact current-session conversation without a formal Goal; it grants no execution permission. scope_change requires an active Goal, task, or an explicitly selected current-session proposal. A proposal is evidence, never approval by itself. If the original request clearly specifies a self-contained task, use relation=new and the action it requests (verify for verification), with the exact current evidenceRefs. Otherwise clarify the intended target with the user. Preserve the original scope, constraints and native permissions.';
30
+ const preparationFieldsSchema = z
31
+ .object({
29
32
  taskType: z.enum(ACTION_TYPES).optional(),
30
33
  requestIntent: RequestIntentSubmissionSchema.optional(),
31
- }).strict();
34
+ })
35
+ .strict();
32
36
  function parsePreparationRequest(value) {
33
37
  const parsed = preparationFieldsSchema.safeParse(value);
34
- return parsed.success && (parsed.data.taskType !== undefined || parsed.data.requestIntent !== undefined) ? parsed.data : undefined;
38
+ return parsed.success &&
39
+ (parsed.data.taskType !== undefined ||
40
+ parsed.data.requestIntent !== undefined)
41
+ ? parsed.data
42
+ : undefined;
35
43
  }
36
44
  function humanMessages(messages) {
37
45
  return messages.filter((value) => value?.role === 'user' && (!value.source || value.source.kind === 'user'));
38
46
  }
39
47
  function humanTask(messages) {
40
- return humanMessages(messages).flatMap(message => typeof message.content === 'string' ? [message.content]
41
- : (message.content ?? []).filter((block) => block.type === 'text').map((block) => block.text)).join('\n').trim();
48
+ return humanMessages(messages)
49
+ .flatMap((message) => typeof message.content === 'string'
50
+ ? [message.content]
51
+ : (message.content ?? [])
52
+ .filter((block) => block.type === 'text')
53
+ .map((block) => block.text))
54
+ .join('\n')
55
+ .trim();
56
+ }
57
+ function humanId(message) {
58
+ return typeof message.id === 'string'
59
+ ? message.id
60
+ : canonicalContentHash(message);
42
61
  }
43
- function humanId(message) { return typeof message.id === 'string' ? message.id : canonicalContentHash(message); }
44
62
  function eventTurn(event) {
45
63
  const data = event?.data;
46
64
  return Number.isSafeInteger(data?.turn) ? data.turn : undefined;
@@ -50,7 +68,9 @@ function automaticPreparationType(intent, requestIntent, name) {
50
68
  if (requestIntent !== undefined && requestIntentIsUnresolved(requestIntent)) {
51
69
  // Gathering evidence grants no change/process authority. An active Goal
52
70
  // still requires its reference to be resolved before opening another run.
53
- return ['read', 'glob', 'grep', 'web_search', 'web_fetch'].includes(name) ? 'research' : undefined;
71
+ return ['read', 'glob', 'grep', 'web_search', 'web_fetch'].includes(name)
72
+ ? 'research'
73
+ : undefined;
54
74
  }
55
75
  if (requestIntent?.action === 'answer')
56
76
  return undefined;
@@ -59,18 +79,39 @@ function automaticPreparationType(intent, requestIntent, name) {
59
79
  return interpreted;
60
80
  if (intent.taskType && intent.taskType !== 'chat')
61
81
  return intent.taskType;
62
- return ['read', 'glob', 'grep', 'web_search', 'web_fetch'].includes(name) ? 'research' : undefined;
82
+ return ['read', 'glob', 'grep', 'web_search', 'web_fetch'].includes(name)
83
+ ? 'research'
84
+ : undefined;
85
+ }
86
+ const NATIVE_EVIDENCE_READS = new Set([
87
+ 'read',
88
+ 'glob',
89
+ 'grep',
90
+ 'web_search',
91
+ 'web_fetch',
92
+ ]);
93
+ const UNOWNED_NATIVE_TOOLS = new Set([
94
+ 'bash',
95
+ 'read',
96
+ 'glob',
97
+ 'grep',
98
+ 'web_search',
99
+ 'web_fetch',
100
+ 'skill',
101
+ ]);
102
+ function denied(reason) {
103
+ return { kind: 'deny', reason };
63
104
  }
64
- const NATIVE_EVIDENCE_READS = new Set(['read', 'glob', 'grep', 'web_search', 'web_fetch']);
65
- const UNOWNED_NATIVE_TOOLS = new Set(['bash', 'read', 'glob', 'grep', 'web_search', 'web_fetch', 'skill']);
66
- function denied(reason) { return { kind: 'deny', reason }; }
67
105
  /** A protocol carrier, not JavaScript evaluation. Only this exact JSON-literal call is recognized. */
68
106
  export function preparationCarrier(value) {
69
107
  if (!value || typeof value !== 'object' || Array.isArray(value))
70
108
  return undefined;
71
109
  const args = value;
72
- if (Object.keys(args).sort().join(',') !== 'code,description' || typeof args.description !== 'string' || !args.description.trim()
73
- || typeof args.code !== 'string' || args.code.length > 8192)
110
+ if (Object.keys(args).sort().join(',') !== 'code,description' ||
111
+ typeof args.description !== 'string' ||
112
+ !args.description.trim() ||
113
+ typeof args.code !== 'string' ||
114
+ args.code.length > 8192)
74
115
  return undefined;
75
116
  const code = args.code.trim().replace(/;$/u, '');
76
117
  const prefix = 'return await tools.prepare_requested_work(';
@@ -93,9 +134,17 @@ export class OnDemandIntake {
93
134
  options;
94
135
  async diagnose(state, observation) {
95
136
  try {
96
- await this.host.diagnostic?.(state.input, { ...observation, ...(state.requestIntentContext ? { context: state.requestIntentContext } : {}), ...(state.requestIntent ? { decision: state.requestIntent } : {}) });
137
+ await this.host.diagnostic?.(state.input, {
138
+ ...observation,
139
+ ...(state.requestIntentContext
140
+ ? { context: state.requestIntentContext }
141
+ : {}),
142
+ ...(state.requestIntent ? { decision: state.requestIntent } : {}),
143
+ });
144
+ }
145
+ catch {
146
+ /* Diagnostic loss cannot authorize or veto work. */
97
147
  }
98
- catch { /* Diagnostic loss cannot authorize or veto work. */ }
99
148
  }
100
149
  #turns = new Map();
101
150
  #clarificationContexts = new WeakMap();
@@ -111,6 +160,7 @@ export class OnDemandIntake {
111
160
  #startupSkillReader;
112
161
  #startupQuestionTool;
113
162
  #questionTools = new WeakMap();
163
+ #planTools = new WeakMap();
114
164
  #skillReaders = new WeakMap();
115
165
  #stopped = false;
116
166
  constructor(host, options = {}) {
@@ -120,35 +170,63 @@ export class OnDemandIntake {
120
170
  /** Exact host-owned implementations, never a name-only execution bypass. */
121
171
  registerControlTool(definition) {
122
172
  this.#controlTools.set(definition.name, definition.execute);
123
- return () => { if (this.#controlTools.get(definition.name) === definition.execute)
124
- this.#controlTools.delete(definition.name); };
173
+ return () => {
174
+ if (this.#controlTools.get(definition.name) === definition.execute)
175
+ this.#controlTools.delete(definition.name);
176
+ };
125
177
  }
126
178
  isControl(execution) {
127
179
  const proof = this.#controlExecutions.get(execution);
128
- return !this.#stopped && !execution.signal.aborted && !!proof
129
- && (!proof.turn || this.bound(execution) === proof.turn && !proof.turn.input.signal.aborted && !['closed', 'stale'].includes(proof.turn.status))
130
- && this.definition(execution)?.execute === proof.execute && canonicalContentHash(execution.arguments) === proof.digest
131
- && (proof.invoke ? [...this.#controlTools].some(([name, invoke]) => invoke === proof.invoke
132
- && this.#tools?.get(name, execution.agent)?.execute === invoke) : this.#controlTools.get(execution.name) === proof.execute || this.conversationControl(execution, this.definition(execution)));
180
+ return (!this.#stopped &&
181
+ !execution.signal.aborted &&
182
+ !!proof &&
183
+ (!proof.turn ||
184
+ (this.bound(execution) === proof.turn &&
185
+ !proof.turn.input.signal.aborted &&
186
+ !['closed', 'stale'].includes(proof.turn.status))) &&
187
+ this.definition(execution)?.execute === proof.execute &&
188
+ canonicalContentHash(execution.arguments) === proof.digest &&
189
+ (proof.invoke
190
+ ? [...this.#controlTools].some(([name, invoke]) => invoke === proof.invoke &&
191
+ this.#tools?.get(name, execution.agent)?.execute === invoke)
192
+ : this.#controlTools.get(execution.name) === proof.execute ||
193
+ this.conversationControl(execution, this.definition(execution))));
133
194
  }
134
195
  controlTask(execution) {
135
- return this.isControl(execution) ? this.#controlExecutions.get(execution)?.turn?.task : undefined;
196
+ return this.isControl(execution)
197
+ ? this.#controlExecutions.get(execution)?.turn?.task
198
+ : undefined;
136
199
  }
137
200
  /** Observational/conversational calls still traverse native policy; never grant effect authority. */
138
201
  conversationControl(execution, actual) {
139
202
  const state = this.bound(execution);
140
- return !!state && state.status === 'answer' && execution.parent === undefined
141
- && (execution.name === 'ask_user_question' && actual.execute === this.#questionTools.get(state.input.agent)
142
- || isCodingWorkflowStatus(actual, execution.arguments));
203
+ return (!!state &&
204
+ state.status === 'answer' &&
205
+ execution.parent === undefined &&
206
+ ((execution.name === 'ask_user_question' &&
207
+ actual.execute === this.#questionTools.get(state.input.agent)) ||
208
+ (execution.name === 'exit_plan_mode' &&
209
+ actual.execute === this.#planTools.get(state.input.agent)) ||
210
+ isCodingWorkflowStatus(actual, execution.arguments)));
143
211
  }
144
212
  controlCall(execution, actual) {
145
- if (this.#controlTools.get(execution.name) === actual.execute || this.conversationControl(execution, actual))
146
- return { execute: actual.execute, digest: canonicalContentHash(execution.arguments) };
147
- if (execution.name !== 'run_code' || !execution.arguments || typeof execution.arguments !== 'object' || Array.isArray(execution.arguments))
213
+ if (this.#controlTools.get(execution.name) === actual.execute ||
214
+ this.conversationControl(execution, actual))
215
+ return {
216
+ execute: actual.execute,
217
+ digest: canonicalContentHash(execution.arguments),
218
+ };
219
+ if (execution.name !== 'run_code' ||
220
+ !execution.arguments ||
221
+ typeof execution.arguments !== 'object' ||
222
+ Array.isArray(execution.arguments))
148
223
  return undefined;
149
224
  const value = execution.arguments;
150
- if (Object.keys(value).sort().join(',') !== 'code,description' || typeof value.code !== 'string' || value.code.length > 8192
151
- || typeof value.description !== 'string' || !value.description.trim())
225
+ if (Object.keys(value).sort().join(',') !== 'code,description' ||
226
+ typeof value.code !== 'string' ||
227
+ value.code.length > 8192 ||
228
+ typeof value.description !== 'string' ||
229
+ !value.description.trim())
152
230
  return undefined;
153
231
  const code = value.code.trim().replace(/;$/u, '');
154
232
  for (const [name, invoke] of this.#controlTools) {
@@ -160,9 +238,16 @@ export class OnDemandIntake {
160
238
  try {
161
239
  const args = JSON.parse(code.slice(prefix.length, -1));
162
240
  if (code === prefix + JSON.stringify(args) + ')')
163
- return { execute: actual.execute, digest: canonicalContentHash(execution.arguments), invoke, args };
241
+ return {
242
+ execute: actual.execute,
243
+ digest: canonicalContentHash(execution.arguments),
244
+ invoke,
245
+ args,
246
+ };
247
+ }
248
+ catch {
249
+ /* Nonliteral programs keep ordinary execution admission. */
164
250
  }
165
- catch { /* Nonliteral programs keep ordinary execution admission. */ }
166
251
  }
167
252
  return undefined;
168
253
  }
@@ -172,7 +257,10 @@ export class OnDemandIntake {
172
257
  return promise;
173
258
  }
174
259
  existingTurn(execution) {
175
- const last = execution.agent?.session?.snapshotEvents?.().filter(event => event.type === 'turn/start' || event.type === 'turn/end').at(-1);
260
+ const last = execution.agent?.session
261
+ ?.snapshotEvents?.()
262
+ .filter((event) => event.type === 'turn/start' || event.type === 'turn/end')
263
+ .at(-1);
176
264
  return last?.type === 'turn/start' ? eventTurn(last) : undefined;
177
265
  }
178
266
  acknowledgeExisting(state) {
@@ -182,42 +270,57 @@ export class OnDemandIntake {
182
270
  const taskType = requestIntentTaskType(state.requestIntent, state.intent.taskType);
183
271
  if (taskType !== undefined)
184
272
  state.taskType = taskType;
185
- state.preparedIntentDigest = canonicalContentHash({ taskType: state.taskType ?? null, requestIntent: state.requestIntent ?? null });
273
+ state.preparedIntentDigest = canonicalContentHash({
274
+ taskType: state.taskType ?? null,
275
+ requestIntent: state.requestIntent ?? null,
276
+ });
186
277
  }
187
278
  /** True means ordinary text may continue; false leaves an existing execution on its original path. */
188
279
  async capture(input) {
189
280
  input.signal.throwIfAborted();
190
281
  if (!this.#stopped && this.host.nativeChild?.(input.agent))
191
282
  return false;
192
- if (this.#stopped || !input.agent.session || !Number.isSafeInteger(input.turn) || input.turn < 1)
283
+ if (this.#stopped ||
284
+ !input.agent.session ||
285
+ !Number.isSafeInteger(input.turn) ||
286
+ input.turn < 1)
193
287
  throw new Error('Invalid on-demand intake identity');
194
- input = { ...input, signal: AbortSignal.any([input.signal, this.#lifecycle.signal]) };
288
+ input = {
289
+ ...input,
290
+ signal: AbortSignal.any([input.signal, this.#lifecycle.signal]),
291
+ };
195
292
  const sessionId = input.agent.session.id;
196
293
  const previous = this.#turns.get(sessionId);
197
294
  if (previous) {
198
- if (previous.input.agent !== input.agent || previous.input.agent.session !== input.agent.session || input.turn < previous.input.turn)
295
+ if (previous.input.agent !== input.agent ||
296
+ previous.input.agent.session !== input.agent.session ||
297
+ input.turn < previous.input.turn)
199
298
  throw new Error('Stale on-demand intake identity');
200
299
  if (previous.input.turn === input.turn) {
201
300
  if (previous.status === 'closed')
202
301
  throw new Error('On-demand turn is closed');
203
- const fresh = humanMessages(input.messages).filter(message => previous.humanContents.get(humanId(message)) !== canonicalContentHash(message));
302
+ const fresh = humanMessages(input.messages).filter((message) => previous.humanContents.get(humanId(message)) !==
303
+ canonicalContentHash(message));
204
304
  if (!fresh.length) {
205
- if (previous.capture && !await previous.capture)
305
+ if (previous.capture && !(await previous.capture))
206
306
  return false;
207
307
  if (previous.status === 'stale')
208
- throw previous.captureError ?? new Error('Previous on-demand request is stale');
308
+ throw (previous.captureError ??
309
+ new Error('Previous on-demand request is stale'));
209
310
  return previous.status !== 'prepared';
210
311
  }
211
312
  previous.status = 'stale';
212
313
  previous.controller.abort(new Error('Human instructions changed'));
213
314
  // A new native batch can contain only the steering message. Preserve
214
315
  // the original request while replacing changed messages by identity.
215
- const merged = new Map(previous.input.messages.map(message => [humanId(message), message]));
316
+ const merged = new Map(previous.input.messages.map((message) => [humanId(message), message]));
216
317
  for (const message of input.messages)
217
318
  merged.set(humanId(message), message);
218
319
  input = { ...input, messages: [...merged.values()] };
219
320
  }
220
- if (previous.input.turn !== input.turn && !humanMessages(input.messages).length && this.host.ready(input.agent)) {
321
+ if (previous.input.turn !== input.turn &&
322
+ !humanMessages(input.messages).length &&
323
+ this.host.ready(input.agent)) {
221
324
  // A host-driven next turn may carry no new human message. Leave its
222
325
  // active execution bound to the host instead of creating an empty intake.
223
326
  if (previous.status === 'closed') {
@@ -229,11 +332,13 @@ export class OnDemandIntake {
229
332
  return false;
230
333
  }
231
334
  }
232
- if (previous.input.turn !== input.turn && !['closed', 'prepared'].includes(previous.status))
335
+ if (previous.input.turn !== input.turn &&
336
+ !['closed', 'prepared'].includes(previous.status))
233
337
  throw new Error('Previous on-demand turn has not ended');
234
338
  previous.controller.abort(new Error('A newer native turn superseded this request'));
235
339
  }
236
- else if (!humanMessages(input.messages).length && this.host.ready(input.agent)) {
340
+ else if (!humanMessages(input.messages).length &&
341
+ this.host.ready(input.agent)) {
237
342
  await this.host.validate(input);
238
343
  input.signal.throwIfAborted();
239
344
  await this.host.advance?.(input);
@@ -242,28 +347,56 @@ export class OnDemandIntake {
242
347
  this.host.answerContext?.retire(input.agent);
243
348
  // Install the pending state synchronously, before any host or classifier awaits.
244
349
  const controller = new AbortController();
245
- const state = { input: { ...input, signal: AbortSignal.any([input.signal, controller.signal]), messages: Object.freeze(structuredClone([...input.messages])) }, task: humanTask(input.messages),
246
- humanContents: new Map(humanMessages(input.messages).map(message => [humanId(message), canonicalContentHash(message)])),
247
- status: 'capturing', intent: { deferInference: true }, memoryOwner: 'none', preparationCalls: new Set(), controller };
350
+ const state = {
351
+ input: {
352
+ ...input,
353
+ signal: AbortSignal.any([input.signal, controller.signal]),
354
+ messages: Object.freeze(structuredClone([...input.messages])),
355
+ },
356
+ task: humanTask(input.messages),
357
+ humanContents: new Map(humanMessages(input.messages).map((message) => [
358
+ humanId(message),
359
+ canonicalContentHash(message),
360
+ ])),
361
+ status: 'capturing',
362
+ intent: { deferInference: true },
363
+ memoryOwner: 'none',
364
+ preparationCalls: new Set(),
365
+ controller,
366
+ };
248
367
  this.#turns.set(sessionId, state);
249
- rememberLispConversation(input.agent, () => !this.options.onlyWhenDeferred && !this.#stopped && this.#turns.get(sessionId) === state
250
- && state.status === 'answer' && !state.input.signal.aborted && this.host.nativeChild?.(input.agent) !== true);
368
+ rememberLispConversation(input.agent, () => !this.options.onlyWhenDeferred &&
369
+ !this.#stopped &&
370
+ this.#turns.get(sessionId) === state &&
371
+ state.status === 'answer' &&
372
+ !state.input.signal.aborted &&
373
+ this.host.nativeChild?.(input.agent) !== true);
251
374
  const promise = (async () => {
252
375
  await this.host.validate(input);
253
376
  this.assertCurrent(state);
254
377
  // Presets and later host plugins register readers after mount. Pin the
255
378
  // validated owner's first reader once, including absence; never rebind it.
256
379
  if (!this.#skillReaders.has(input.agent))
257
- this.#skillReaders.set(input.agent, this.#startupSkillReader ?? this.#tools?.get('skill', input.agent)?.execute);
380
+ this.#skillReaders.set(input.agent, this.#startupSkillReader ??
381
+ this.#tools?.get('skill', input.agent)?.execute);
258
382
  if (!this.#questionTools.has(input.agent))
259
- this.#questionTools.set(input.agent, this.#startupQuestionTool ?? this.#tools?.get('ask_user_question', input.agent)?.execute);
383
+ this.#questionTools.set(input.agent, this.#startupQuestionTool ??
384
+ this.#tools?.get('ask_user_question', input.agent)?.execute);
385
+ if (!this.#planTools.has(input.agent))
386
+ this.#planTools.set(input.agent, this.#tools?.get('exit_plan_mode', input.agent)?.execute);
260
387
  if (!humanMessages(input.messages).length)
261
388
  throw new Error('Original human input is required before on-demand intake');
262
389
  // Native authority is independent of optional memory, Skills and model
263
390
  // classification. Never manufacture an owned task merely to run Bash.
264
- if (!this.options.onlyWhenDeferred && await this.host.nativeOwnership?.(state.input) === 'unowned') {
391
+ if (!this.options.onlyWhenDeferred &&
392
+ (await this.host.nativeOwnership?.(state.input)) === 'unowned') {
393
+ this.assertCurrent(state);
394
+ const context = (await this.host.requestContext?.(state.input, state.task)) ??
395
+ createRequestIntentContext({
396
+ task: state.task,
397
+ messageRefs: requestIntentMessageRefs(state.input.messages, state.task),
398
+ });
265
399
  this.assertCurrent(state);
266
- const context = createRequestIntentContext({ task: state.task, messageRefs: requestIntentMessageRefs(state.input.messages, state.task) });
267
400
  await this.host.prepareNativeCoding?.(state.input, state.task, context);
268
401
  this.assertCurrent(state);
269
402
  state.nativeUnowned = true;
@@ -298,8 +431,10 @@ export class OnDemandIntake {
298
431
  const pendingClarification = this.#clarifications.get(sessionId);
299
432
  await this.diagnose(state, { kind: 'interpretation' });
300
433
  await this.diagnose(state, { kind: 'resume' });
301
- if (pendingClarification?.agent === input.agent && state.requestIntent && !requestIntentIsUnresolved(state.requestIntent)
302
- && !executionClarification(state.task, state.requestIntent, state.requestIntentContext))
434
+ if (pendingClarification?.agent === input.agent &&
435
+ state.requestIntent &&
436
+ !requestIntentIsUnresolved(state.requestIntent) &&
437
+ !executionClarification(state.task, state.requestIntent, state.requestIntentContext))
303
438
  this.#clarifications.delete(sessionId);
304
439
  state.memoryOwner = memoryOwnerKind(state.requestIntent, state.intent.taskType);
305
440
  this.assertCurrent(state);
@@ -307,10 +442,16 @@ export class OnDemandIntake {
307
442
  await this.host.advance?.(input, state.requestIntent);
308
443
  this.assertCurrent(state);
309
444
  const resumeConfirmation = previous?.input.turn !== input.turn && state.task
310
- ? await this.host.resumeConfirmation?.(input) ?? false : false;
311
- if (process.env.KIOKUKO_INTENT_DEBUG === '1' && previous?.input.turn !== input.turn) {
312
- console.error('on-demand confirmation route', { turn: input.turn, previousTurn: previous?.input.turn,
313
- hasTask: state.task.length > 0, resumeConfirmation });
445
+ ? ((await this.host.resumeConfirmation?.(input)) ?? false)
446
+ : false;
447
+ if (process.env.KIOKUKO_INTENT_DEBUG === '1' &&
448
+ previous?.input.turn !== input.turn) {
449
+ console.error('on-demand confirmation route', {
450
+ turn: input.turn,
451
+ previousTurn: previous?.input.turn,
452
+ hasTask: state.task.length > 0,
453
+ resumeConfirmation,
454
+ });
314
455
  }
315
456
  if (resumeConfirmation) {
316
457
  this.assertCurrent(state);
@@ -321,19 +462,27 @@ export class OnDemandIntake {
321
462
  this.host.answerContext?.retire(input.agent);
322
463
  return false;
323
464
  }
324
- if (process.env.KIOKUKO_INTENT_DEBUG === '1' && previous?.input.turn !== input.turn && state.task && this.host.resumeConfirmation) {
465
+ if (process.env.KIOKUKO_INTENT_DEBUG === '1' &&
466
+ previous?.input.turn !== input.turn &&
467
+ state.task &&
468
+ this.host.resumeConfirmation) {
325
469
  console.error('confirmation resume not selected', input.turn, state.task.length);
326
470
  }
327
- if (state.task && await this.host.replay?.(state.input)) {
471
+ if (state.task && (await this.host.replay?.(state.input))) {
328
472
  this.acknowledgeExisting(state);
329
473
  return false;
330
474
  }
331
475
  const assessmentConsistent = state.requestIntent && state.requestIntent.action !== 'answer'
332
- ? await this.assess(state, state.requestIntent) : true;
333
- const canContinue = assessmentConsistent && state.requestIntent?.relation === 'same_scope_continuation'
334
- && !requestIntentIsUnresolved(state.requestIntent) && state.requestIntent.action !== 'answer';
335
- if (previous?.input.turn !== input.turn && state.task && canContinue
336
- && await this.host.existing(input, state.requestIntent)) {
476
+ ? await this.assess(state, state.requestIntent)
477
+ : true;
478
+ const canContinue = assessmentConsistent &&
479
+ state.requestIntent?.relation === 'same_scope_continuation' &&
480
+ !requestIntentIsUnresolved(state.requestIntent) &&
481
+ state.requestIntent.action !== 'answer';
482
+ if (previous?.input.turn !== input.turn &&
483
+ state.task &&
484
+ canContinue &&
485
+ (await this.host.existing(input, state.requestIntent))) {
337
486
  // Persisted ownership is only an acknowledgement when this exact native
338
487
  // agent/session/turn is already bound. A cold resume stays conversational
339
488
  // until explicit preparation reconstructs that binding.
@@ -344,16 +493,25 @@ export class OnDemandIntake {
344
493
  }
345
494
  // Eager mode still binds known work before generation. Classifier uncertainty
346
495
  // instead reaches ordinary reasoning, with no execution grant or purpose UI.
347
- const requestIntentUnresolved = state.requestIntent !== undefined
348
- && requestIntentIsUnresolved(state.requestIntent);
496
+ const requestIntentUnresolved = state.requestIntent !== undefined &&
497
+ requestIntentIsUnresolved(state.requestIntent);
349
498
  const requestAsAnswer = state.requestIntent?.action === 'answer';
350
499
  const knownIntent = this.host.interpret === undefined
351
- ? !state.intent.deferInference && state.intent.taskType !== undefined && state.intent.taskType !== 'chat'
352
- : state.requestIntent !== undefined && !requestIntentUnresolved && !requestAsAnswer
353
- && state.requestIntent.relation !== 'unknown' && state.requestIntent.action !== 'unknown';
354
- const missingExecutionDetail = executionClarification(state.task, state.requestIntent, state.requestIntentContext)
355
- || state.requestIntentContext?.activeTask && !taskCanExecute(state.requestIntentContext.activeTask);
356
- if (this.options.onlyWhenDeferred && knownIntent && !missingExecutionDetail && assessmentConsistent) {
500
+ ? !state.intent.deferInference &&
501
+ state.intent.taskType !== undefined &&
502
+ state.intent.taskType !== 'chat'
503
+ : state.requestIntent !== undefined &&
504
+ !requestIntentUnresolved &&
505
+ !requestAsAnswer &&
506
+ state.requestIntent.relation !== 'unknown' &&
507
+ state.requestIntent.action !== 'unknown';
508
+ const missingExecutionDetail = executionClarification(state.task, state.requestIntent, state.requestIntentContext) ||
509
+ (state.requestIntentContext?.activeTask &&
510
+ !taskCanExecute(state.requestIntentContext.activeTask));
511
+ if (this.options.onlyWhenDeferred &&
512
+ knownIntent &&
513
+ !missingExecutionDetail &&
514
+ assessmentConsistent) {
357
515
  this.#turns.delete(sessionId);
358
516
  state.status = 'closed';
359
517
  controller.abort(new Error('Known intent continues through eager admission'));
@@ -391,16 +549,22 @@ export class OnDemandIntake {
391
549
  }
392
550
  assertCurrent(state) {
393
551
  state.input.signal.throwIfAborted();
394
- if (this.#stopped || this.#turns.get(state.input.agent.session.id) !== state || ['closed', 'stale'].includes(state.status))
552
+ if (this.#stopped ||
553
+ this.#turns.get(state.input.agent.session.id) !== state ||
554
+ ['closed', 'stale'].includes(state.status))
395
555
  throw new Error('On-demand request changed or closed');
396
556
  }
397
557
  bound(execution) {
398
558
  const agent = execution.agent, state = agent?.session && this.#turns.get(agent.session.id);
399
- if (!state || state.input.agent !== agent || state.input.agent.session !== agent.session)
559
+ if (!state ||
560
+ state.input.agent !== agent ||
561
+ state.input.agent.session !== agent.session)
400
562
  return undefined;
401
563
  if (state.existing && this.existingTurn(execution) !== state.input.turn)
402
564
  return undefined;
403
- const starts = agent.session.snapshotEvents?.().filter(event => event.type === 'turn/start');
565
+ const starts = agent.session
566
+ .snapshotEvents?.()
567
+ .filter((event) => event.type === 'turn/start');
404
568
  const latest = eventTurn(starts?.at(-1));
405
569
  if (latest !== undefined && latest !== state.input.turn)
406
570
  return undefined;
@@ -408,8 +572,12 @@ export class OnDemandIntake {
408
572
  }
409
573
  ownerIdentity(state) {
410
574
  const session = state.input.agent.session;
411
- return { agentId: state.input.agent.id, sessionId: session.id, turn: state.input.turn,
412
- requestDigest: canonicalContentHash(state.task) };
575
+ return {
576
+ agentId: state.input.agent.id,
577
+ sessionId: session.id,
578
+ turn: state.input.turn,
579
+ requestDigest: canonicalContentHash(state.task),
580
+ };
413
581
  }
414
582
  definition(execution) {
415
583
  try {
@@ -417,8 +585,11 @@ export class OnDemandIntake {
417
585
  if (registered)
418
586
  return registered;
419
587
  const transport = this.#executorTransports.get(execution);
420
- return transport && transport.current() === transport.runtime && transport.runtime.run === transport.definition.execute
421
- ? transport.definition : undefined;
588
+ return transport &&
589
+ transport.current() === transport.runtime &&
590
+ transport.runtime.run === transport.definition.execute
591
+ ? transport.definition
592
+ : undefined;
422
593
  }
423
594
  catch {
424
595
  return undefined;
@@ -428,31 +599,51 @@ export class OnDemandIntake {
428
599
  * The runtime function is an identity check; dispatch still belongs to DSH.
429
600
  * A native or missing executor cannot obtain a preparation-only carrier here. */
430
601
  async bindExecutorTransport(execution, context) {
431
- if (execution.name !== 'run_code' || execution.parent !== undefined || typeof execution.token !== 'symbol'
432
- || typeof this.#tools?.setExecutor !== 'function' || !context.get)
602
+ if (execution.name !== 'run_code' ||
603
+ execution.parent !== undefined ||
604
+ typeof execution.token !== 'symbol' ||
605
+ typeof this.#tools?.setExecutor !== 'function' ||
606
+ !context.get)
433
607
  return;
434
608
  // Cordis returns fresh contextual proxies; compare the underlying service,
435
609
  // never invoke it outside the selected executor's dispatch.
436
610
  const owner = execution.agent?.ctx;
437
611
  const services = typeof owner?.get === 'function' ? owner : context;
438
- const current = () => { const value = services.get('ptcRuntime', false); return value?.[symbols.original] ?? value; };
612
+ const current = () => {
613
+ const value = services.get('ptcRuntime', false);
614
+ return value?.[symbols.original] ?? value;
615
+ };
439
616
  const runtime = current(), prompt = services.get('systemPrompt', false);
440
- if (typeof runtime?.run !== 'function' || typeof prompt?.assemble !== 'function')
617
+ if (typeof runtime?.run !== 'function' ||
618
+ typeof prompt?.assemble !== 'function')
441
619
  return;
442
- const projected = await prompt.assemble({ scope: execution.agent, agent: execution.agent });
620
+ const projected = await prompt.assemble({
621
+ scope: execution.agent,
622
+ agent: execution.agent,
623
+ });
443
624
  execution.signal.throwIfAborted();
444
- if (current() !== runtime || projected.tools.length !== 1 || projected.tools[0]?.name !== 'run_code'
445
- || !projected.sections.some((section) => section.name === 'tools:sdk'))
625
+ if (current() !== runtime ||
626
+ projected.tools.length !== 1 ||
627
+ projected.tools[0]?.name !== 'run_code' ||
628
+ !projected.sections.some((section) => section.name === 'tools:sdk'))
446
629
  return;
447
- this.#executorTransports.set(execution, { definition: { name: 'run_code', execute: runtime.run }, runtime, current });
630
+ this.#executorTransports.set(execution, {
631
+ definition: { name: 'run_code', execute: runtime.run },
632
+ runtime,
633
+ current,
634
+ });
448
635
  }
449
636
  async assess(state, decision) {
450
- if (!this.host.assess || decision?.action === 'answer' || decision?.action === 'research' || requestIntentIsUnresolved(decision))
637
+ if (!this.host.assess ||
638
+ decision?.action === 'answer' ||
639
+ decision?.action === 'research' ||
640
+ requestIntentIsUnresolved(decision))
451
641
  return true;
452
642
  if (!decision || !state.requestIntentContext)
453
643
  throw new RequestClarification('The source or candidate is unresolved', true);
454
644
  const context = state.requestIntentContext, digest = canonicalContentHash([context, decision]);
455
- if (state.assessment?.digest !== digest) {
645
+ if (state.assessment?.digest !== digest ||
646
+ state.assessment.result.outcome === 'unavailable') {
456
647
  const result = await this.host.assess(state.input, context, decision);
457
648
  this.assertCurrent(state);
458
649
  if (!result) {
@@ -473,19 +664,33 @@ export class OnDemandIntake {
473
664
  throw new ExecutionSelectionPending();
474
665
  const resolved = requestIntent ?? state.requestIntent;
475
666
  try {
476
- await this.host.diagnostic?.(state.input, { kind: 'interpretation', ...(state.requestIntentContext ? { context: state.requestIntentContext } : {}), ...(resolved ? { decision: resolved } : {}) });
667
+ await this.host.diagnostic?.(state.input, {
668
+ kind: 'interpretation',
669
+ ...(state.requestIntentContext
670
+ ? { context: state.requestIntentContext }
671
+ : {}),
672
+ ...(resolved ? { decision: resolved } : {}),
673
+ });
674
+ }
675
+ catch {
676
+ /* Advisory only. */
477
677
  }
478
- catch { /* Advisory only. */ }
479
678
  if (resolved?.action === 'change' && resolved.changeScope === 'unknown') {
480
679
  throw new RequestClarification('The requested change scope is unresolved. Specify implementation or document using the current request evidence before preparing work.');
481
680
  }
482
- if (resolved && requestIntentIsUnresolved(resolved) && (state.requestIntentContext?.activeGoal || state.requestIntentContext?.activeTask)) {
681
+ if (resolved &&
682
+ requestIntentIsUnresolved(resolved) &&
683
+ (state.requestIntentContext?.activeGoal ||
684
+ state.requestIntentContext?.activeTask)) {
483
685
  throw new RequestClarification('Clarify the request before preparing work');
484
686
  }
485
- if (!await this.assess(state, resolved))
687
+ if (!(await this.assess(state, resolved)))
486
688
  throw new RequestClarification('Independent source comparison or finite-domain verification requires clarification', true);
487
689
  const activeTask = state.requestIntentContext?.activeTask;
488
- if (activeTask && resolved?.relation === 'same_scope_continuation' && !taskCanExecute(activeTask) && !resolved.taskOperation) {
690
+ if (activeTask &&
691
+ resolved?.relation === 'same_scope_continuation' &&
692
+ !taskCanExecute(activeTask) &&
693
+ !resolved.taskOperation) {
489
694
  throw new RequestClarification('The current task requires an explicit user-selected state operation before execution.');
490
695
  }
491
696
  const clarification = executionClarification(state.task, resolved, state.requestIntentContext);
@@ -493,13 +698,21 @@ export class OnDemandIntake {
493
698
  throw new RequestClarification(clarification);
494
699
  if (!ACTION_TYPES.includes(taskType))
495
700
  throw new Error('Answer conversational questions directly; prepare_requested_work is for requested work');
496
- if (state.status === 'prepared' && state.confirmationResume && explicitIntent
497
- && (state.requestIntent?.relation === 'unknown' || state.requestIntent?.action === 'unknown'))
701
+ if (state.status === 'prepared' &&
702
+ state.confirmationResume &&
703
+ explicitIntent &&
704
+ (state.requestIntent?.relation === 'unknown' ||
705
+ state.requestIntent?.action === 'unknown'))
498
706
  state.status = 'answer';
499
707
  if (state.status === 'prepared') {
500
708
  if (state.taskType !== taskType)
501
709
  throw requestInterpretationError('Prepared intent cannot be replaced within a turn', state.requestIntent, state.requestIntentContext, true);
502
- if (explicitIntent && state.preparedIntentDigest !== canonicalContentHash({ taskType, requestIntent: requestIntent ?? null })) {
710
+ if (explicitIntent &&
711
+ state.preparedIntentDigest !==
712
+ canonicalContentHash({
713
+ taskType,
714
+ requestIntent: requestIntent ?? null,
715
+ })) {
503
716
  throw requestInterpretationError('Prepared interpretation cannot be replaced within a turn', state.requestIntent, state.requestIntentContext, true);
504
717
  }
505
718
  return;
@@ -514,7 +727,10 @@ export class OnDemandIntake {
514
727
  state.taskType = taskType;
515
728
  const operation = (async () => {
516
729
  // Retain exact original messages. The only caller-supplied input is an advisory task type.
517
- const admitted = await this.host.prepare({ ...state.input, signal: AbortSignal.any([state.input.signal, signal]) }, taskType, requestIntent ?? state.requestIntent, state.requestIntentContext);
730
+ const admitted = await this.host.prepare({
731
+ ...state.input,
732
+ signal: AbortSignal.any([state.input.signal, signal]),
733
+ }, taskType, requestIntent ?? state.requestIntent, state.requestIntentContext);
518
734
  this.assertCurrent(state);
519
735
  signal.throwIfAborted();
520
736
  if (!admitted || !this.host.ready(state.input.agent, state.input.turn))
@@ -522,8 +738,12 @@ export class OnDemandIntake {
522
738
  const preparedIntent = requestIntent ?? state.requestIntent;
523
739
  if (preparedIntent !== undefined)
524
740
  state.requestIntent = preparedIntent;
525
- state.preparedIntentDigest = canonicalContentHash({ taskType, requestIntent: state.requestIntent ?? null });
741
+ state.preparedIntentDigest = canonicalContentHash({
742
+ taskType,
743
+ requestIntent: state.requestIntent ?? null,
744
+ });
526
745
  state.status = 'prepared';
746
+ this.#clarifications.delete(state.input.agent.session.id);
527
747
  delete state.confirmationResume;
528
748
  })();
529
749
  this.watch(operation);
@@ -535,11 +755,17 @@ export class OnDemandIntake {
535
755
  if (!['closed', 'stale'].includes(state.status)) {
536
756
  // A declined selection preserves this request for a conversational acknowledgement.
537
757
  // It is not admission: preparation remains consumed and tools still need a ready host.
538
- state.selectionPending = error instanceof ExecutionSelectionPending && !state.input.signal.aborted && !signal.aborted;
758
+ state.selectionPending =
759
+ error instanceof ExecutionSelectionPending &&
760
+ !state.input.signal.aborted &&
761
+ !signal.aborted;
539
762
  // Preparation failure is not a change of human authority. Keep the
540
763
  // captured request retryable, but never revive Stop/cancellation. A
541
764
  // new call must pass host admission and native permission checks again.
542
- state.status = state.input.signal.aborted || signal.aborted || this.#stopped ? 'stale' : 'answer';
765
+ state.status =
766
+ state.input.signal.aborted || signal.aborted || this.#stopped
767
+ ? 'stale'
768
+ : 'answer';
543
769
  delete state.preparedIntentDigest;
544
770
  delete state.taskType;
545
771
  state.memoryOwner = 'none';
@@ -555,9 +781,14 @@ export class OnDemandIntake {
555
781
  const pending = agent.session && this.#clarifications.get(agent.session.id);
556
782
  if (this.#stopped || pending?.agent !== agent)
557
783
  return false;
558
- const boundary = agent.session?.snapshotEvents?.().filter(event => event.type === 'turn/end').at(-1);
784
+ const boundary = agent.session
785
+ ?.snapshotEvents?.()
786
+ .filter((event) => event.type === 'turn/end')
787
+ .at(-1);
559
788
  const data = boundary?.data;
560
- if (data?.turn !== undefined && data.turn >= pending.turn && ['aborted', 'error', 'max-tokens', 'blocked'].includes(data.reason?.kind ?? '')) {
789
+ if (data?.turn !== undefined &&
790
+ data.turn >= pending.turn &&
791
+ ['aborted', 'error', 'max-tokens', 'blocked'].includes(data.reason?.kind ?? '')) {
561
792
  this.#clarifications.delete(agent.session.id);
562
793
  return false;
563
794
  }
@@ -565,67 +796,210 @@ export class OnDemandIntake {
565
796
  }
566
797
  async clarify(state, executionSignal, reason) {
567
798
  this.assertCurrent(state);
568
- const signal = AbortSignal.any([executionSignal, state.input.signal, state.controller.signal]);
799
+ const signal = AbortSignal.any([
800
+ executionSignal,
801
+ state.input.signal,
802
+ state.controller.signal,
803
+ ]);
569
804
  const context = state.requestIntentContext;
570
805
  const sessionId = state.input.agent.session.id;
571
- if (reason.assessment && state.assessment?.result.outcome === 'unavailable') {
806
+ if (reason.interpretation)
807
+ return {
808
+ prepared: false,
809
+ status: 'request_interpretation_required',
810
+ originalTask: state.task,
811
+ requestInterpretation: requestInterpretationRecovery(state.requestIntent, context),
812
+ currentReferences: context
813
+ ? requestIntentRequiredReferenceIds(context)
814
+ : [],
815
+ permissions: 'unchanged_native_policy',
816
+ nextAction: 'submit_current_request_intent',
817
+ detail: 'Interpret the captured user request and current host references in the main model. A taskType label alone is not an action or approval. Ask the human only if the action or referenced target is actually unclear.',
818
+ };
819
+ if (reason.assessment &&
820
+ state.assessment?.result.outcome === 'unavailable') {
572
821
  const a = state.assessment.result;
573
- return { prepared: false, status: 'assessment_unavailable', semantic: a.semantic, detector: a.detector.state === 'unavailable' ? a.detector.reason : a.detector.result.status,
574
- originalTask: state.task, permissions: 'unchanged_native_policy', preparedExecutionAllowed: false, nextAction: 'report_assessment_unavailable',
575
- recovery: 'Preserve the request and report the unavailable assessment. Do not repeat clarification, claim preparation succeeded, or switch tools to bypass this preparation boundary.', detail: '原文照合または有限領域検査を利用できません。実行は準備されていません。' };
822
+ return {
823
+ prepared: false,
824
+ status: 'assessment_unavailable',
825
+ semantic: a.semantic,
826
+ detector: a.detector.state === 'unavailable'
827
+ ? a.detector.reason
828
+ : a.detector.result.status,
829
+ originalTask: state.task,
830
+ permissions: 'unchanged_native_policy',
831
+ preparedExecutionAllowed: false,
832
+ nextAction: 'report_assessment_unavailable',
833
+ recovery: 'Preserve the request and report the unavailable assessment. Do not repeat clarification, claim preparation succeeded, or switch tools to bypass this preparation boundary.',
834
+ detail: '原文照合または有限領域検査を利用できません。実行は準備されていません。',
835
+ };
576
836
  }
577
- const scope = state.requestIntent?.action === 'change' && state.requestIntent.changeScope === 'unknown'
578
- || reason.message.startsWith('The requested change scope');
837
+ const scope = (state.requestIntent?.action === 'change' &&
838
+ state.requestIntent.changeScope === 'unknown') ||
839
+ reason.message.startsWith('The requested change scope');
579
840
  const target = !!executionClarification(state.task, state.requestIntent, context);
580
841
  const transition = context?.activeTask && !taskCanExecute(context.activeTask);
581
- const question = reason.assessment ? '現在の依頼と実行候補の対象・操作・制約を照合できませんでした。今回の対象と希望する操作、以前の指示を変更する範囲を教えてください。'
582
- : transition ? `現在のタスクは ${context.activeTask.state} です。続けるために、どの操作を希望しますか?(${taskAllowedOperations(context.activeTask).join(' / ')})`
583
- : target ? 'どの具体的なファイル・項目・相手・操作を対象にしますか?'
584
- : scope ? '変更するのは文章だけですか、それともコード・テスト・設定も含みますか?'
585
- : state.requestIntent?.action === 'unknown' ? 'その対象に、具体的に何をしてほしいですか?'
842
+ const question = reason.assessment
843
+ ? '現在の依頼と実行候補の対象・操作・制約を照合できませんでした。今回の対象と希望する操作、以前の指示を変更する範囲を教えてください。'
844
+ : transition
845
+ ? `現在のタスクは ${context.activeTask.state} です。続けるために、どの操作を希望しますか?(${taskAllowedOperations(context.activeTask).join(' / ')})`
846
+ : target
847
+ ? 'どの具体的なファイル・項目・相手・操作を対象にしますか?'
848
+ : scope
849
+ ? '変更するのは文章だけですか、それともコード・テスト・設定も含みますか?'
850
+ : state.requestIntent?.action === 'unknown'
851
+ ? 'その対象に、具体的に何をしてほしいですか?'
586
852
  : '今回の依頼は現在の作業の続きですか、それとも別の作業ですか?';
587
- const replies = this.#clarificationContexts.get(state.input.agent) ?? new Map();
588
- const clarification = collectClarificationContext(humanMessages(state.input.messages).map(message => ({ id: humanId(message), text: humanTask([message]) })), replies);
589
- const answerScope = canonicalContentHash({ activeTask: context?.activeTask ?? null, activeGoal: context?.activeGoal ?? null, proposals: context?.proposals ?? [] });
853
+ const replies = this.#clarificationContexts.get(state.input.agent) ??
854
+ new Map();
855
+ const clarification = collectClarificationContext(humanMessages(state.input.messages).map((message) => ({
856
+ id: humanId(message),
857
+ text: humanTask([message]),
858
+ })), replies);
859
+ const answerScope = canonicalContentHash({
860
+ activeTask: context?.activeTask ?? null,
861
+ activeGoal: context?.activeGoal ?? null,
862
+ proposals: context?.proposals ?? [],
863
+ });
590
864
  const answered = reusableClarificationAnswer(clarification, question, answerScope);
591
865
  if (answered)
592
- return { prepared: false, status: 'clarification_answer_available', question, questionId: answered.questionId, answer: answered.value,
593
- originalTask: clarification.sources.map(source => source.text).join('\n'), permissions: 'unchanged_native_policy',
594
- currentReferences: context ? requestIntentRequiredReferenceIds(context) : [], nextAction: 'submit_current_request_intent' };
595
- const key = canonicalContentHash({ context: context ?? null, task: state.task, humanEvidence: [...state.humanContents], question });
866
+ this.#clarifications.delete(sessionId);
867
+ if (answered && reason.assessment)
868
+ return {
869
+ prepared: false,
870
+ status: 'assessment_rejected',
871
+ semantic: state.assessment?.result.semantic,
872
+ semanticIssues: state.assessment?.result.semanticIssues ?? [],
873
+ detector: state.assessment?.result.detector.state === 'available'
874
+ ? state.assessment.result.detector.result.status
875
+ : state.assessment?.result.detector.reason,
876
+ originalTask: state.task,
877
+ currentReferences: context
878
+ ? requestIntentRequiredReferenceIds(context)
879
+ : [],
880
+ permissions: 'unchanged_native_policy',
881
+ nextAction: 'revise_request_intent_or_report_conflict',
882
+ detail: 'The current candidate still conflicts with, or is not established by, the source including the latest answer. Revise the candidate using that answer or explain the specific unresolved conflict. Do not repeat an identical submission or the answered question.',
883
+ };
884
+ if (answered)
885
+ return {
886
+ prepared: false,
887
+ status: 'clarification_answer_available',
888
+ question,
889
+ questionId: answered.questionId,
890
+ answer: answered.value,
891
+ originalTask: clarification.sources
892
+ .map((source) => source.text)
893
+ .join('\n'),
894
+ permissions: 'unchanged_native_policy',
895
+ currentReferences: context
896
+ ? requestIntentRequiredReferenceIds(context)
897
+ : [],
898
+ nextAction: 'submit_current_request_intent',
899
+ };
900
+ const key = canonicalContentHash({
901
+ context: context ?? null,
902
+ task: state.task,
903
+ humanEvidence: [...state.humanContents],
904
+ question,
905
+ });
596
906
  const previous = this.#clarifications.get(sessionId);
597
907
  if (previous?.agent === state.input.agent && previous.key === key)
598
908
  return previous.operation ?? previous.result;
599
909
  const id = `request-clarification-${key.slice(0, 20)}`;
600
- const result = { prepared: false, status: 'awaiting_clarification', question, questionId: id, originalTask: state.task,
601
- permissions: 'unchanged_native_policy', nextAction: this.host.questions ? 'wait_for_user' : 'ask_user_once' };
602
- const pending = { agent: state.input.agent, turn: state.input.turn, key, status: 'waiting', result: result,
603
- operation: undefined };
910
+ const result = {
911
+ prepared: false,
912
+ status: 'awaiting_clarification',
913
+ question,
914
+ questionId: id,
915
+ originalTask: state.task,
916
+ ...(requestIntentIsUnresolved(state.requestIntent)
917
+ ? {
918
+ requestInterpretation: requestInterpretationRecovery(state.requestIntent, context),
919
+ }
920
+ : {}),
921
+ ...(reason.assessment
922
+ ? {
923
+ semantic: state.assessment?.result.semantic,
924
+ semanticIssues: state.assessment?.result.semanticIssues ?? [],
925
+ }
926
+ : {}),
927
+ permissions: 'unchanged_native_policy',
928
+ nextAction: this.host.questions ? 'wait_for_user' : 'ask_user_once',
929
+ };
930
+ const pending = {
931
+ agent: state.input.agent,
932
+ turn: state.input.turn,
933
+ key,
934
+ status: 'waiting',
935
+ result: result,
936
+ operation: undefined,
937
+ };
604
938
  this.#clarifications.set(sessionId, pending);
605
- await this.diagnose(state, { kind: 'question', questionRef: id, reason: transition ? 'transition_required' : target ? 'target_unknown' : scope ? 'scope_unknown' : 'clarification_required' });
939
+ await this.diagnose(state, {
940
+ kind: 'question',
941
+ questionRef: id,
942
+ reason: transition
943
+ ? 'transition_required'
944
+ : target
945
+ ? 'target_unknown'
946
+ : scope
947
+ ? 'scope_unknown'
948
+ : 'clarification_required',
949
+ });
606
950
  if (!this.host.questions || !state.input.agent.followup)
607
951
  return result;
608
952
  const operation = (async () => {
609
953
  try {
610
- const response = await abortable(this.host.questions.ask({ agent: state.input.agent, signal,
611
- questions: [{ id, question, ...(scope && !target && !transition ? { options: [{ label: '文章のみ' }, { label: 'コード・テスト・設定を含む' }] } : {}) }] }), signal);
954
+ const response = await abortable(this.host.questions.ask({
955
+ agent: state.input.agent,
956
+ signal,
957
+ questions: [
958
+ {
959
+ id,
960
+ question,
961
+ ...(scope && !target && !transition
962
+ ? {
963
+ options: [
964
+ { label: '文章のみ' },
965
+ { label: 'コード・テスト・設定を含む' },
966
+ ],
967
+ }
968
+ : {}),
969
+ },
970
+ ],
971
+ }), signal);
612
972
  signal.throwIfAborted();
613
973
  this.assertCurrent(state);
614
- if (this.#clarifications.get(sessionId) !== pending || pending.status !== 'waiting')
974
+ if (this.#clarifications.get(sessionId) !== pending ||
975
+ pending.status !== 'waiting')
615
976
  return result;
616
977
  await this.host.validate(state.input);
617
978
  signal.throwIfAborted();
618
979
  this.assertCurrent(state);
619
- const current = await this.host.interpret?.(state.input, state.task);
980
+ const current = this.host.requestContext
981
+ ? await this.host.requestContext(state.input, state.task)
982
+ : (await this.host.interpret?.(state.input, state.task))?.context;
620
983
  signal.throwIfAborted();
621
984
  this.assertCurrent(state);
622
- if (context && (!current || canonicalContentHash(current.context) !== canonicalContentHash(context))) {
985
+ if (context &&
986
+ (!current ||
987
+ canonicalContentHash(current) !== canonicalContentHash(context))) {
623
988
  if (current) {
624
- state.requestIntentContext = current.context;
625
- state.requestIntent = current.decision;
626
- state.requestIntentContextDigest = canonicalContentHash(current.context);
989
+ state.requestIntentContext = current;
990
+ state.requestIntent = unresolvedRequestIntent(current, 'clarification_context_changed');
991
+ state.requestIntentContextDigest = canonicalContentHash(current);
627
992
  }
628
- pending.result = { ...result, ...(current ? { currentReferences: requestIntentRequiredReferenceIds(current.context) } : {}), status: 'clarification_context_changed', detail: 'The task or current evidence changed while awaiting the answer. Refresh the current request before asking again.' };
993
+ pending.result = {
994
+ ...result,
995
+ ...(current
996
+ ? {
997
+ currentReferences: requestIntentRequiredReferenceIds(current),
998
+ }
999
+ : {}),
1000
+ status: 'clarification_context_changed',
1001
+ detail: 'The task or current evidence changed while awaiting the answer. Refresh the current request before asking again.',
1002
+ };
629
1003
  return pending.result;
630
1004
  }
631
1005
  const answer = response.answers[0];
@@ -636,17 +1010,38 @@ export class OnDemandIntake {
636
1010
  // label the actual UI answer separately, and re-enter ordinary native
637
1011
  // capture. The reply itself is never mapped to execution authority.
638
1012
  const answerMessageId = randomUUID();
639
- const reply = clarificationReply(clarification, { id: answerMessageId, questionId: id, question, value, scope: answerScope });
1013
+ const reply = clarificationReply(clarification, {
1014
+ id: answerMessageId,
1015
+ questionId: id,
1016
+ question,
1017
+ value,
1018
+ scope: answerScope,
1019
+ });
640
1020
  const content = reply.content;
641
1021
  signal.throwIfAborted();
642
1022
  this.assertCurrent(state);
643
1023
  replies.set(answerMessageId, reply.context);
644
1024
  this.#clarificationContexts.set(state.input.agent, replies);
645
- state.input.agent.followup({ id: answerMessageId, role: 'user', source: { kind: 'user' }, content: [{ type: 'text', text: content }] });
646
- await this.diagnose(state, { kind: 'answer', questionRef: id, answerRef: answerMessageId });
1025
+ state.input.agent.followup({
1026
+ id: answerMessageId,
1027
+ role: 'user',
1028
+ source: { kind: 'user' },
1029
+ content: [{ type: 'text', text: content }],
1030
+ });
1031
+ await this.diagnose(state, {
1032
+ kind: 'answer',
1033
+ questionRef: id,
1034
+ answerRef: answerMessageId,
1035
+ });
647
1036
  pending.status = 'queued';
648
- pending.result = { ...result, status: 'clarification_answer_queued', answer: value, answerRef: answerMessageId,
649
- nextAction: 'await_queued_answer_capture', detail: 'The answer is already queued as native user input. Do not ask it again or reuse old references. Continue only after the host captures the queued answer and supplies its current references.' };
1037
+ pending.result = {
1038
+ ...result,
1039
+ status: 'clarification_answer_queued',
1040
+ answer: value,
1041
+ answerRef: answerMessageId,
1042
+ nextAction: 'await_queued_answer_capture',
1043
+ detail: 'The answer is already queued as native user input. Do not ask it again or reuse old references. Continue only after the host captures the queued answer and supplies its current references.',
1044
+ };
650
1045
  return pending.result;
651
1046
  }
652
1047
  catch (error) {
@@ -655,8 +1050,18 @@ export class OnDemandIntake {
655
1050
  this.#clarifications.delete(sessionId);
656
1051
  throw error;
657
1052
  }
658
- // A dismissed/unavailable question surface remains waiting, never approved.
659
- return result;
1053
+ // Dismissal remains unapproved. A failed surface or host read can be
1054
+ // retried explicitly instead of retaining a dead pending question.
1055
+ if (error?.code === 'ASK_CANCELLED')
1056
+ return result;
1057
+ if (this.#clarifications.get(sessionId) === pending)
1058
+ this.#clarifications.delete(sessionId);
1059
+ return {
1060
+ ...result,
1061
+ status: 'clarification_unavailable',
1062
+ nextAction: 'retry_preparation',
1063
+ detail: 'The question or its current context could not be read. No work was prepared and no answer was queued. Retry explicitly after restoring the question surface or host storage.',
1064
+ };
660
1065
  }
661
1066
  finally {
662
1067
  pending.operation = undefined;
@@ -665,24 +1070,50 @@ export class OnDemandIntake {
665
1070
  pending.operation = operation;
666
1071
  return this.watch(operation);
667
1072
  }
668
- bindIntent(state, submission) {
1073
+ async bindIntent(state, submission) {
669
1074
  if (!submission) {
670
- if (state.requestIntent?.action === 'change' && state.requestIntent.changeScope === 'unknown') {
1075
+ if (requestIntentIsUnresolved(state.requestIntent) &&
1076
+ this.host.interpret) {
1077
+ const current = await this.host.interpret(state.input, state.task);
1078
+ this.assertCurrent(state);
1079
+ if (current) {
1080
+ state.requestIntentContext = current.context;
1081
+ state.requestIntentContextDigest = canonicalContentHash(current.context);
1082
+ state.requestIntent = current.decision;
1083
+ }
1084
+ }
1085
+ if (requestIntentIsUnresolved(state.requestIntent) &&
1086
+ state.requestIntentContext &&
1087
+ state.requestIntent?.source === 'unresolved' &&
1088
+ state.requestIntent.unresolvedReason !==
1089
+ 'provider_abstained_or_uncertain' &&
1090
+ !this.#clarifications.has(state.input.agent.session.id)) {
1091
+ throw new RequestClarification('The main model must interpret the current request before preparing work', false, true);
1092
+ }
1093
+ if (state.requestIntent?.action === 'change' &&
1094
+ state.requestIntent.changeScope === 'unknown') {
671
1095
  throw new RequestClarification('The requested change scope is unresolved. Specify implementation or document using the current request evidence before preparing work.');
672
1096
  }
673
1097
  return state.requestIntent;
674
1098
  }
675
- if (!state.requestIntentContext || state.requestIntentContextDigest !== canonicalContentHash(state.requestIntentContext)) {
1099
+ if (!state.requestIntentContext ||
1100
+ state.requestIntentContextDigest !==
1101
+ canonicalContentHash(state.requestIntentContext)) {
676
1102
  throw new Error('Request interpretation context is stale');
677
1103
  }
678
1104
  const result = validateMainModelRequestIntent(submission, state.requestIntentContext);
679
1105
  if (!result.ok) {
680
1106
  switch (result.reason) {
681
- case 'no_active_goal': throw new Error(`Request relation ${submission.relation} cannot be used without an active Goal. ${NO_ACTIVE_GOAL_GUIDANCE}`);
682
- case 'missing_reference': throw new Error('Request interpretation is missing required current message or active Goal references. Use the exact current evidenceRefs from the supplied context.');
683
- case 'unknown_reference': throw new Error('Request interpretation references another request, stale revision, or unknown proposal. Use only the exact current evidenceRefs from the supplied context.');
684
- case 'unresolved_intent': throw new RequestClarification('Request relation or action is unresolved. Interpret the original request and current context; clarify with the user if its action or target remains unclear.');
685
- case 'invalid_submission': throw new Error('Invalid request interpretation. Supply a relation, action and unique exact current evidenceRefs.');
1107
+ case 'no_active_goal':
1108
+ throw new Error(`Request relation ${submission.relation} cannot be used without an active Goal. ${NO_ACTIVE_GOAL_GUIDANCE}`);
1109
+ case 'missing_reference':
1110
+ throw new Error('Request interpretation is missing required current message or active Goal references. Use the exact current evidenceRefs from the supplied context.');
1111
+ case 'unknown_reference':
1112
+ throw new Error('Request interpretation references another request, stale revision, or unknown proposal. Use only the exact current evidenceRefs from the supplied context.');
1113
+ case 'unresolved_intent':
1114
+ throw new RequestClarification('Request relation or action is unresolved. Interpret the original request and current context; clarify with the user if its action or target remains unclear.');
1115
+ case 'invalid_submission':
1116
+ throw new Error('Invalid request interpretation. Supply a relation, action and unique exact current evidenceRefs.');
686
1117
  case 'invalid_task_operation': {
687
1118
  const task = state.requestIntentContext.activeTask;
688
1119
  if (!task)
@@ -697,7 +1128,9 @@ export class OnDemandIntake {
697
1128
  throw new RequestClarification('The requested change scope is unresolved. Specify implementation or document using the current request evidence before preparing work.');
698
1129
  }
699
1130
  const prior = state.requestIntent;
700
- if (prior?.source === 'decision-provider' && prior.relation !== 'unknown' && prior.action !== 'unknown') {
1131
+ if (prior?.source === 'decision-provider' &&
1132
+ prior.relation !== 'unknown' &&
1133
+ prior.action !== 'unknown') {
701
1134
  if (prior.relation !== bound.relation || prior.action !== bound.action) {
702
1135
  throw new Error('Main-model interpretation conflicts with the configured decision provider');
703
1136
  }
@@ -711,55 +1144,118 @@ export class OnDemandIntake {
711
1144
  // Project the immutable human batch captured before that waterfall.
712
1145
  if (state?.input.turn === input.turn && state.input.agent !== input.agent)
713
1146
  throw new Error('Answer projection native agent changed');
714
- const projected = state?.input.turn === input.turn && !state.nativeUnowned && state.answerContextReady && !state.confirmationResume && this.host.answerContext
715
- ? await this.host.answerContext.project(state.input, messages, this.ownerIdentity(state)) : messages;
1147
+ const projected = state?.input.turn === input.turn &&
1148
+ !state.nativeUnowned &&
1149
+ state.answerContextReady &&
1150
+ !state.confirmationResume &&
1151
+ this.host.answerContext
1152
+ ? await this.host.answerContext.project(state.input, messages, this.ownerIdentity(state))
1153
+ : messages;
716
1154
  return this.guidance(projected, state?.input.turn === input.turn ? state : undefined);
717
1155
  }
718
1156
  async beforeRequest(agent) {
719
1157
  const state = agent.session && this.#turns.get(agent.session.id);
720
- if (state && !state.nativeUnowned && state.answerContextReady && state.input.agent === agent && state.status === 'answer' && this.pending(agent)) {
1158
+ if (state &&
1159
+ !state.nativeUnowned &&
1160
+ state.answerContextReady &&
1161
+ state.input.agent === agent &&
1162
+ state.status === 'answer' &&
1163
+ this.pending(agent)) {
721
1164
  await this.host.answerContext?.beforeRequest(state.input, this.ownerIdentity(state));
722
1165
  }
723
1166
  }
724
1167
  async fence(agent, request) {
725
1168
  const state = agent.session && this.#turns.get(agent.session.id);
726
- if (state && !state.nativeUnowned && state.answerContextReady && state.input.agent === agent && state.status === 'answer' && this.pending(agent)) {
1169
+ if (state &&
1170
+ !state.nativeUnowned &&
1171
+ state.answerContextReady &&
1172
+ state.input.agent === agent &&
1173
+ state.status === 'answer' &&
1174
+ this.pending(agent)) {
727
1175
  await this.host.answerContext?.fence(state.input, request, this.ownerIdentity(state));
728
1176
  }
729
1177
  }
730
1178
  guidance(messages, state) {
731
1179
  const context = state?.requestIntentContext;
732
1180
  const interpretation = state?.requestIntent;
733
- const intentText = context ? `\n\nCurrent request references and same-session work (host-bound, advisory only):\n${JSON.stringify({
734
- references: requestIntentReferenceIds(context),
735
- requiredReferences: requestIntentRequiredReferenceIds(context),
736
- activeGoal: context.activeGoal ?? null,
737
- activeTask: context.activeTask ?? null,
738
- proposals: context.proposals.map(({ id, revision, digest, text }) => ({ id, revision, digest, text,
739
- evidenceRef: `proposal:${canonicalContentHash([id, revision, digest])}` })),
740
- decision: interpretation ? { relation: interpretation.relation, action: interpretation.action, source: interpretation.source,
741
- policyVersion: interpretation.policyVersion, evidence: interpretation.evidence, unresolvedReason: interpretation.unresolvedReason ?? null } : null,
742
- })}\nInclude every requiredReferences entry in requestIntent.evidenceRefs. Add a proposal evidenceRef only when the current user request actually adopts or refers to that proposal; do not copy all proposals automatically. Use only these exact references. The original human message remains unchanged.` : '';
743
- const text = ON_DEMAND_GUIDANCE + intentText + (context && !context.activeGoal && !context.activeTask ? `\n${NO_ACTIVE_GOAL_GUIDANCE}` : '');
744
- return [...messages, { id: randomUUID(), role: 'user', content: [{ type: 'text', text }],
745
- source: { kind: KIOKUKO_DSH_SOURCE_KIND, form: 'snapshot', sections: [{ name: 'on-demand-intake', text }] } }];
1181
+ const intentText = context
1182
+ ? `\n\nCurrent request references and same-session work (host-bound, advisory only):\n${JSON.stringify({
1183
+ references: requestIntentReferenceIds(context),
1184
+ requiredReferences: requestIntentRequiredReferenceIds(context),
1185
+ activeGoal: context.activeGoal ?? null,
1186
+ activeTask: context.activeTask ?? null,
1187
+ proposals: context.proposals.map(({ id, revision, digest, text }) => ({
1188
+ id,
1189
+ revision,
1190
+ digest,
1191
+ text,
1192
+ evidenceRef: `proposal:${canonicalContentHash([id, revision, digest])}`,
1193
+ })),
1194
+ decision: interpretation
1195
+ ? {
1196
+ relation: interpretation.relation,
1197
+ action: interpretation.action,
1198
+ source: interpretation.source,
1199
+ policyVersion: interpretation.policyVersion,
1200
+ evidence: interpretation.evidence,
1201
+ unresolvedReason: interpretation.unresolvedReason ?? null,
1202
+ }
1203
+ : null,
1204
+ })}\nInclude every requiredReferences entry in requestIntent.evidenceRefs. Add a proposal evidenceRef only when the current user request actually adopts or refers to that proposal; do not copy all proposals automatically. Use only these exact references. The original human message remains unchanged.`
1205
+ : '';
1206
+ const text = ON_DEMAND_GUIDANCE +
1207
+ intentText +
1208
+ (context && !context.activeGoal && !context.activeTask
1209
+ ? `\n${NO_ACTIVE_GOAL_GUIDANCE}`
1210
+ : '');
1211
+ return [
1212
+ ...messages,
1213
+ {
1214
+ id: randomUUID(),
1215
+ role: 'user',
1216
+ content: [{ type: 'text', text }],
1217
+ source: {
1218
+ kind: KIOKUKO_DSH_SOURCE_KIND,
1219
+ form: 'snapshot',
1220
+ sections: [{ name: 'on-demand-intake', text }],
1221
+ },
1222
+ },
1223
+ ];
746
1224
  }
747
1225
  snapshot(sessionId) {
748
1226
  const state = this.#turns.get(sessionId);
749
- return state && { task: state.task, taskType: state.taskType ?? state.intent.taskType ?? null, status: state.status, turn: state.input.turn,
750
- ...(state.requestIntent ? { requestIntent: structuredClone(state.requestIntent) } : {}), memoryOwner: state.memoryOwner };
1227
+ return (state && {
1228
+ task: state.task,
1229
+ taskType: state.taskType ?? state.intent.taskType ?? null,
1230
+ status: state.status,
1231
+ turn: state.input.turn,
1232
+ ...(state.requestIntent
1233
+ ? { requestIntent: structuredClone(state.requestIntent) }
1234
+ : {}),
1235
+ memoryOwner: state.memoryOwner,
1236
+ });
751
1237
  }
752
1238
  /** Refresh only authority facts after a committed control operation; retain the exact human request. */
753
1239
  taskStateChanged(task) {
754
1240
  const state = this.#turns.get(task.sessionId);
755
- if (!state || ['closed', 'stale'].includes(state.status) || state.input.signal.aborted || !state.requestIntentContext)
1241
+ if (!state ||
1242
+ ['closed', 'stale'].includes(state.status) ||
1243
+ state.input.signal.aborted ||
1244
+ !state.requestIntentContext)
756
1245
  return;
757
1246
  state.nativeUnowned = false;
758
1247
  const previous = state.requestIntentContext.activeTask;
759
- if (previous?.taskId === task.taskId && previous.revision === task.revision && previous.generation === task.generation)
1248
+ if (previous?.taskId === task.taskId &&
1249
+ previous.revision === task.revision &&
1250
+ previous.generation === task.generation)
760
1251
  return;
761
- const context = createRequestIntentContext({ ...state.requestIntentContext, activeTask: task,
762
- ...(state.requestIntentContext.activeGoal?.runId === task.runId ? {} : { activeGoal: undefined }) });
1252
+ const context = createRequestIntentContext({
1253
+ ...state.requestIntentContext,
1254
+ activeTask: task,
1255
+ ...(state.requestIntentContext.activeGoal?.runId === task.runId
1256
+ ? {}
1257
+ : { activeGoal: undefined }),
1258
+ });
763
1259
  if (!context)
764
1260
  return;
765
1261
  state.requestIntentContext = context;
@@ -774,19 +1270,40 @@ export class OnDemandIntake {
774
1270
  /** Exact registered native-tool proof, never a model flag or a generic task-control bypass. */
775
1271
  unownedNative(execution) {
776
1272
  const state = this.bound(execution), proof = this.#executions.get(execution);
777
- return !this.#stopped && !execution.signal.aborted && UNOWNED_NATIVE_TOOLS.has(execution.name) && execution.parent === undefined
778
- && !!state && state.nativeUnowned === true && state.status === 'answer' && !state.input.signal.aborted
779
- && !!proof && proof.turn === state && proof.execute === this.definition(execution)?.execute
780
- && proof.nativeUnownedDigest === canonicalContentHash(execution.arguments);
1273
+ return (!this.#stopped &&
1274
+ !execution.signal.aborted &&
1275
+ UNOWNED_NATIVE_TOOLS.has(execution.name) &&
1276
+ execution.parent === undefined &&
1277
+ !!state &&
1278
+ state.nativeUnowned === true &&
1279
+ state.status === 'answer' &&
1280
+ !state.input.signal.aborted &&
1281
+ !!proof &&
1282
+ proof.turn === state &&
1283
+ proof.execute === this.definition(execution)?.execute &&
1284
+ proof.nativeUnownedDigest === canonicalContentHash(execution.arguments));
781
1285
  }
782
1286
  /** Direct native Bash follows native permissions, independently of optional task intake. */
783
1287
  nativeCommand(execution) {
784
1288
  const state = this.bound(execution), actual = this.definition(execution), proof = this.#executions.get(execution);
785
- if (!this.options.onlyWhenDeferred && !this.#stopped && actual && isLispNativeCommand(execution, actual.execute))
786
- return !proof || proof.turn === state && proof.execute === actual.execute && proof.nativeCommandDigest === canonicalContentHash(execution.arguments);
787
- if (this.options.onlyWhenDeferred || this.#stopped || execution.signal.aborted || execution.name !== 'bash'
788
- || execution.parent !== undefined || !state || state.input.signal.aborted || !actual
789
- || execution.agent && this.host.nativeChild?.(execution.agent))
1289
+ if (!this.options.onlyWhenDeferred &&
1290
+ !this.#stopped &&
1291
+ actual &&
1292
+ isLispNativeCommand(execution, actual.execute))
1293
+ return (!proof ||
1294
+ (proof.turn === state &&
1295
+ proof.execute === actual.execute &&
1296
+ proof.nativeCommandDigest ===
1297
+ canonicalContentHash(execution.arguments)));
1298
+ if (this.options.onlyWhenDeferred ||
1299
+ this.#stopped ||
1300
+ execution.signal.aborted ||
1301
+ execution.name !== 'bash' ||
1302
+ execution.parent !== undefined ||
1303
+ !state ||
1304
+ state.input.signal.aborted ||
1305
+ !actual ||
1306
+ (execution.agent && this.host.nativeChild?.(execution.agent)))
790
1307
  return false;
791
1308
  try {
792
1309
  this.assertCurrent(state);
@@ -794,15 +1311,24 @@ export class OnDemandIntake {
794
1311
  catch {
795
1312
  return false;
796
1313
  }
797
- return !proof || proof.turn === state && proof.execute === actual.execute
798
- && proof.nativeCommandDigest === canonicalContentHash(execution.arguments);
1314
+ return (!proof ||
1315
+ (proof.turn === state &&
1316
+ proof.execute === actual.execute &&
1317
+ proof.nativeCommandDigest === canonicalContentHash(execution.arguments)));
799
1318
  }
800
1319
  /** Delegate real root Lisp tools to their own effect/lease/approval authority. */
801
1320
  nativeLisp(execution) {
802
1321
  const state = this.bound(execution), actual = this.definition(execution), proof = this.#executions.get(execution);
803
- if (this.options.onlyWhenDeferred || this.#stopped || execution.signal.aborted || execution.parent !== undefined
804
- || !state || state.status !== 'answer' || state.input.signal.aborted || !actual || !isLispImplementation(actual.execute)
805
- || execution.agent && this.host.nativeChild?.(execution.agent))
1322
+ if (this.options.onlyWhenDeferred ||
1323
+ this.#stopped ||
1324
+ execution.signal.aborted ||
1325
+ execution.parent !== undefined ||
1326
+ !state ||
1327
+ state.status !== 'answer' ||
1328
+ state.input.signal.aborted ||
1329
+ !actual ||
1330
+ !isLispImplementation(actual.execute) ||
1331
+ (execution.agent && this.host.nativeChild?.(execution.agent)))
806
1332
  return false;
807
1333
  try {
808
1334
  this.assertCurrent(state);
@@ -810,35 +1336,63 @@ export class OnDemandIntake {
810
1336
  catch {
811
1337
  return false;
812
1338
  }
813
- return !proof || proof.turn === state && proof.execute === actual.execute
814
- && proof.nativeLispDigest === canonicalContentHash(execution.arguments);
1339
+ return (!proof ||
1340
+ (proof.turn === state &&
1341
+ proof.execute === actual.execute &&
1342
+ proof.nativeLispDigest === canonicalContentHash(execution.arguments)));
815
1343
  }
816
1344
  preparationOnly(execution) {
817
1345
  if (this.isControl(execution))
818
1346
  return true;
819
1347
  const state = this.bound(execution), proof = this.#executions.get(execution);
820
- if (this.#stopped || execution.signal.aborted || execution.parent !== undefined || !state || state.input.signal.aborted
821
- || !proof || proof.turn !== state || this.definition(execution)?.execute !== proof.execute
822
- || state.preparationCalls.has(execution.callId))
1348
+ if (this.#stopped ||
1349
+ execution.signal.aborted ||
1350
+ execution.parent !== undefined ||
1351
+ !state ||
1352
+ state.input.signal.aborted ||
1353
+ !proof ||
1354
+ proof.turn !== state ||
1355
+ this.definition(execution)?.execute !== proof.execute ||
1356
+ state.preparationCalls.has(execution.callId))
823
1357
  return false;
824
1358
  if (proof.carrier)
825
- return execution.name === 'run_code' && state.status === 'answer'
826
- && canonicalContentHash(preparationCarrier(execution.arguments) ?? null) === proof.carrierDigest;
827
- return proof.requestPreparation === true && execution.name === TASK_PREPARE_TOOL
828
- && (state.status === 'answer' || state.existing === true && state.status === 'prepared' && this.host.ready(state.input.agent, state.input.turn));
1359
+ return (execution.name === 'run_code' &&
1360
+ state.status === 'answer' &&
1361
+ canonicalContentHash(preparationCarrier(execution.arguments) ?? null) === proof.carrierDigest);
1362
+ return (proof.requestPreparation === true &&
1363
+ execution.name === TASK_PREPARE_TOOL &&
1364
+ (state.status === 'answer' ||
1365
+ (state.existing === true &&
1366
+ state.status === 'prepared' &&
1367
+ this.host.ready(state.input.agent, state.input.turn))));
829
1368
  }
830
1369
  pending(agent) {
831
1370
  const state = agent.session && this.#turns.get(agent.session.id);
832
- return !!state && !state.confirmationResume && state.input.agent === agent && !['prepared', 'closed', 'stale'].includes(state.status);
1371
+ return (!!state &&
1372
+ !state.confirmationResume &&
1373
+ state.input.agent === agent &&
1374
+ !['prepared', 'closed', 'stale'].includes(state.status));
833
1375
  }
834
1376
  /** An admitted host continuation still needs capture to retire the previous turn's receipt. */
835
- continuing(agent) { return !this.#stopped && this.host.ready(agent); }
1377
+ continuing(agent) {
1378
+ return !this.#stopped && this.host.ready(agent);
1379
+ }
836
1380
  /** Exact captured task at a native lifecycle boundary, never a later selected task. */
837
1381
  boundaryTask(session, turn) {
838
1382
  const state = this.#turns.get(session.id), task = state?.requestIntentContext?.activeTask;
839
- if (state?.input.agent.session !== session || state.input.turn !== turn || !task || task.state !== 'Working')
1383
+ if (state?.input.agent.session !== session ||
1384
+ state.input.turn !== turn ||
1385
+ !task ||
1386
+ task.state !== 'Working')
840
1387
  return undefined;
841
- return { agent: state.input.agent, expected: { taskId: task.taskId, revision: task.revision, generation: task.generation } };
1388
+ return {
1389
+ agent: state.input.agent,
1390
+ expected: {
1391
+ taskId: task.taskId,
1392
+ revision: task.revision,
1393
+ generation: task.generation,
1394
+ },
1395
+ };
842
1396
  }
843
1397
  finish(session, turn) {
844
1398
  const state = this.#turns.get(session.id);
@@ -858,37 +1412,66 @@ export class OnDemandIntake {
858
1412
  // Trust the validated host registry, not a later same-name replacement.
859
1413
  this.#startupQuestionTool = tools.get('ask_user_question')?.execute;
860
1414
  if (systemPrompt && !this.options.onlyWhenDeferred)
861
- this.#disposers.push(systemPrompt.section({ name: 'kiokuko:on-demand-intake', order: -99999, text: 'On-demand host contract: text answers do not require execution intake. The public prepare_requested_work tool accepts an advisory taskType or a source-bound requestIntent; task_prepare/task_answer remain host-only. ' + ON_DEMAND_GUIDANCE }));
862
- const definition = { name: TASK_PREPARE_TOOL, modelFacing: true,
1415
+ this.#disposers.push(systemPrompt.section({
1416
+ name: 'kiokuko:on-demand-intake',
1417
+ order: -99999,
1418
+ text: 'On-demand host contract: text answers do not require execution intake. The public prepare_requested_work tool accepts an advisory taskType or a source-bound requestIntent; task_prepare/task_answer remain host-only. ' +
1419
+ ON_DEMAND_GUIDANCE,
1420
+ }));
1421
+ const definition = {
1422
+ name: TASK_PREPARE_TOOL,
1423
+ modelFacing: true,
863
1424
  description: 'Prepare the exact original user request for tool-backed work. Supply either legacy advisory taskType or requestIntent with exact evidenceRefs from the host context; never rewrite the task or invent a target. This does not grant native tool permissions. Answer ordinary questions directly; clarify unknown actions or targets first.',
864
- parameters: { ...JSON.parse(JSON.stringify(z.toJSONSchema(preparationFieldsSchema))), anyOf: [{ required: ['taskType'] }, { required: ['requestIntent'] }] },
865
- output: { schema: {}, render: (_args, value) => [{ type: 'text', text: JSON.stringify(value) }] },
1425
+ parameters: {
1426
+ ...JSON.parse(JSON.stringify(z.toJSONSchema(preparationFieldsSchema))),
1427
+ anyOf: [{ required: ['taskType'] }, { required: ['requestIntent'] }],
1428
+ },
1429
+ output: {
1430
+ schema: {},
1431
+ render: (_args, value) => [
1432
+ { type: 'text', text: JSON.stringify(value) },
1433
+ ],
1434
+ },
866
1435
  execute: async (args, execution) => {
867
1436
  const state = this.bound(execution);
868
1437
  const value = parsePreparationRequest(args);
869
- if (!state || execution.name !== TASK_PREPARE_TOOL || this.definition(execution)?.execute !== definition.execute || !value)
1438
+ if (!state ||
1439
+ execution.name !== TASK_PREPARE_TOOL ||
1440
+ this.definition(execution)?.execute !== definition.execute ||
1441
+ !value)
870
1442
  throw new Error('Invalid task preparation request');
871
- if (state.preparationCalls.has(execution.callId) || !(state.status === 'answer' || state.status === 'prepared' && this.host.ready(state.input.agent, state.input.turn)))
1443
+ if (state.preparationCalls.has(execution.callId) ||
1444
+ !(state.status === 'answer' ||
1445
+ (state.status === 'prepared' &&
1446
+ this.host.ready(state.input.agent, state.input.turn))))
872
1447
  throw new Error('Task preparation was already consumed for this turn');
873
1448
  state.preparationCalls.add(execution.callId);
874
1449
  try {
875
1450
  if (state.existing) {
876
1451
  this.assertCurrent(state);
877
1452
  execution.signal.throwIfAborted();
878
- const boundIntent = this.bindIntent(state, value.requestIntent);
879
- if (boundIntent?.relation === 'same_scope_continuation' && !requestIntentIsUnresolved(boundIntent) && boundIntent.action !== 'answer') {
1453
+ const boundIntent = await this.bindIntent(state, value.requestIntent);
1454
+ if (boundIntent?.relation === 'same_scope_continuation' &&
1455
+ !requestIntentIsUnresolved(boundIntent) &&
1456
+ boundIntent.action !== 'answer') {
880
1457
  const intentType = requestIntentTaskType(boundIntent, state.taskType ?? state.intent.taskType ?? value.taskType);
881
- if (value.taskType !== undefined && intentType !== undefined && value.taskType !== intentType) {
1458
+ if (value.taskType !== undefined &&
1459
+ intentType !== undefined &&
1460
+ value.taskType !== intentType) {
882
1461
  throw new Error('The advisory taskType conflicts with the bound request action');
883
1462
  }
884
1463
  state.requestIntent = boundIntent;
885
1464
  }
886
1465
  else {
887
- if (!boundIntent || requestIntentIsUnresolved(boundIntent) || boundIntent.action === 'answer') {
1466
+ if (!boundIntent ||
1467
+ requestIntentIsUnresolved(boundIntent) ||
1468
+ boundIntent.action === 'answer') {
888
1469
  throw new RequestClarification('Clarify whether this request continues, changes, or starts work before preparing it');
889
1470
  }
890
1471
  const intentType = requestIntentTaskType(boundIntent, state.taskType ?? state.intent.taskType ?? value.taskType);
891
- if (!intentType || intentType === 'chat' || value.taskType !== undefined && value.taskType !== intentType) {
1472
+ if (!intentType ||
1473
+ intentType === 'chat' ||
1474
+ (value.taskType !== undefined && value.taskType !== intentType)) {
892
1475
  throw new Error('The advisory taskType conflicts with the bound request action');
893
1476
  }
894
1477
  state.existing = false;
@@ -898,43 +1481,68 @@ export class OnDemandIntake {
898
1481
  }
899
1482
  }
900
1483
  else {
901
- const suppliedIntent = this.bindIntent(state, value.requestIntent);
1484
+ const suppliedIntent = await this.bindIntent(state, value.requestIntent);
902
1485
  const currentIntent = suppliedIntent ?? state.requestIntent;
903
- const unresolved = currentIntent !== undefined && requestIntentIsUnresolved(currentIntent);
904
- if (unresolved && (state.requestIntentContext?.activeGoal || state.requestIntentContext?.activeTask || value.requestIntent !== undefined || value.taskType === undefined)) {
1486
+ const unresolved = currentIntent !== undefined &&
1487
+ requestIntentIsUnresolved(currentIntent);
1488
+ if (unresolved) {
905
1489
  throw new RequestClarification('Clarify the request before preparing work');
906
1490
  }
907
- if (state.requestIntentContext?.activeGoal && currentIntent === undefined) {
1491
+ if (state.requestIntentContext?.activeGoal &&
1492
+ currentIntent === undefined) {
908
1493
  throw new RequestClarification('Clarify whether this request continues, changes, or starts the active Goal');
909
1494
  }
910
- const intentType = unresolved ? undefined : requestIntentTaskType(currentIntent, state.taskType ?? state.intent.taskType ?? value.taskType);
911
- if (value.taskType !== undefined && intentType !== undefined && value.taskType !== intentType) {
1495
+ const intentType = unresolved
1496
+ ? undefined
1497
+ : requestIntentTaskType(currentIntent, state.taskType ?? state.intent.taskType ?? value.taskType);
1498
+ if (value.taskType !== undefined &&
1499
+ intentType !== undefined &&
1500
+ value.taskType !== intentType) {
912
1501
  throw new Error('The advisory taskType conflicts with the bound request action');
913
1502
  }
914
1503
  const taskType = intentType ?? value.taskType;
915
- if (!taskType || taskType === 'chat' || !ACTION_TYPES.includes(taskType))
1504
+ if (!taskType ||
1505
+ taskType === 'chat' ||
1506
+ !ACTION_TYPES.includes(taskType))
916
1507
  throw new RequestClarification('Answer ordinary questions directly; clarify unknown actions before preparing work');
917
1508
  state.memoryOwner = memoryOwnerKind(currentIntent, taskType);
918
1509
  await this.prepare(state, taskType, execution.signal, currentIntent, value.requestIntent !== undefined);
919
1510
  }
920
- return { prepared: true, taskType: state.taskType ?? value.taskType ?? requestIntentTaskType(state.requestIntent), originalTask: state.task,
921
- ...(state.requestIntent ? { requestIntent: state.requestIntent } : {}),
922
- ...(state.requestIntent && requestIntentIsUnresolved(state.requestIntent) ? { requestInterpretation: requestInterpretationRecovery(state.requestIntent, state.requestIntentContext, true) } : {}),
923
- memoryOwner: state.memoryOwner, permissions: 'unchanged_native_policy' };
1511
+ return {
1512
+ prepared: true,
1513
+ taskType: state.taskType ??
1514
+ value.taskType ??
1515
+ requestIntentTaskType(state.requestIntent),
1516
+ originalTask: state.task,
1517
+ ...(state.requestIntent
1518
+ ? { requestIntent: state.requestIntent }
1519
+ : {}),
1520
+ ...(state.requestIntent &&
1521
+ requestIntentIsUnresolved(state.requestIntent)
1522
+ ? {
1523
+ requestInterpretation: requestInterpretationRecovery(state.requestIntent, state.requestIntentContext, true),
1524
+ }
1525
+ : {}),
1526
+ memoryOwner: state.memoryOwner,
1527
+ permissions: 'unchanged_native_policy',
1528
+ };
924
1529
  }
925
1530
  catch (error) {
926
1531
  if (!(error instanceof RequestClarification))
927
1532
  throw error;
928
1533
  return this.clarify(state, execution.signal, error);
929
1534
  }
930
- } };
1535
+ },
1536
+ };
931
1537
  this.#disposers.push(tools.register(definition));
932
1538
  this.#disposers.push(context.on('tools/pre-execute', async (execution, next) => {
933
1539
  if (this.#stopped || execution.signal.aborted)
934
1540
  return denied('On-demand intake is stopped or cancelled');
935
1541
  if (execution.agent && this.host.nativeChild?.(execution.agent)) {
936
1542
  const decision = await next();
937
- return decision?.kind === 'cancel' ? denied('Native tool policy cancelled this call') : decision;
1543
+ return decision?.kind === 'cancel'
1544
+ ? denied('Native tool policy cancelled this call')
1545
+ : decision;
938
1546
  }
939
1547
  await this.bindExecutorTransport(execution, context);
940
1548
  const actual = this.definition(execution);
@@ -943,7 +1551,9 @@ export class OnDemandIntake {
943
1551
  const control = this.controlCall(execution, actual);
944
1552
  if (control) {
945
1553
  const turn = this.bound(execution);
946
- if (execution.agent?.session && this.#turns.has(execution.agent.session.id) && !turn)
1554
+ if (execution.agent?.session &&
1555
+ this.#turns.has(execution.agent.session.id) &&
1556
+ !turn)
947
1557
  return denied('Stale task control request');
948
1558
  if (turn) {
949
1559
  try {
@@ -953,70 +1563,127 @@ export class OnDemandIntake {
953
1563
  return denied('Stale task control request');
954
1564
  }
955
1565
  }
956
- this.#controlExecutions.set(execution, { ...control, ...(turn ? { turn } : {}) });
1566
+ this.#controlExecutions.set(execution, {
1567
+ ...control,
1568
+ ...(turn ? { turn } : {}),
1569
+ });
957
1570
  const decision = await next();
958
- return decision?.kind === 'cancel' ? denied('Native tool policy cancelled this call') : decision;
1571
+ return decision?.kind === 'cancel'
1572
+ ? denied('Native tool policy cancelled this call')
1573
+ : decision;
959
1574
  }
960
1575
  const state = this.bound(execution);
961
1576
  if (!state) {
962
- if (execution.agent?.session && this.#turns.has(execution.agent.session.id))
1577
+ if (execution.agent?.session &&
1578
+ this.#turns.has(execution.agent.session.id))
963
1579
  return denied('Stale on-demand native turn');
964
1580
  const turn = this.existingTurn(execution);
965
- if (!execution.agent || turn === undefined || !this.host.ready(execution.agent, turn))
1581
+ if (!execution.agent ||
1582
+ turn === undefined ||
1583
+ !this.host.ready(execution.agent, turn))
966
1584
  return denied('No current native request for tool execution');
967
1585
  const decision = await next();
968
- return decision?.kind === 'cancel' ? denied('Native tool policy cancelled this call') : decision;
1586
+ return decision?.kind === 'cancel'
1587
+ ? denied('Native tool policy cancelled this call')
1588
+ : decision;
969
1589
  }
970
1590
  try {
971
1591
  this.assertCurrent(state);
972
- if (state.confirmationResume && execution.name !== TASK_PREPARE_TOOL && this.host.ready(state.input.agent, state.input.turn))
1592
+ if (state.confirmationResume &&
1593
+ execution.name !== TASK_PREPARE_TOOL &&
1594
+ this.host.ready(state.input.agent, state.input.turn))
973
1595
  this.acknowledgeExisting(state);
974
1596
  if (this.nativeCommand(execution)) {
975
- this.#executions.set(execution, { turn: state, execute: actual.execute, nativeCommandDigest: canonicalContentHash(execution.arguments) });
1597
+ this.#executions.set(execution, {
1598
+ turn: state,
1599
+ execute: actual.execute,
1600
+ nativeCommandDigest: canonicalContentHash(execution.arguments),
1601
+ });
976
1602
  const decision = await next();
977
- return decision?.kind === 'cancel' ? denied('Native tool policy cancelled this call') : decision;
1603
+ return decision?.kind === 'cancel'
1604
+ ? denied('Native tool policy cancelled this call')
1605
+ : decision;
978
1606
  }
979
1607
  if (this.nativeLisp(execution)) {
980
- this.#executions.set(execution, { turn: state, execute: actual.execute, nativeLispDigest: canonicalContentHash(execution.arguments) });
1608
+ this.#executions.set(execution, {
1609
+ turn: state,
1610
+ execute: actual.execute,
1611
+ nativeLispDigest: canonicalContentHash(execution.arguments),
1612
+ });
981
1613
  const decision = await next();
982
- return decision?.kind === 'cancel' ? denied('Native tool policy cancelled this call') : decision;
1614
+ return decision?.kind === 'cancel'
1615
+ ? denied('Native tool policy cancelled this call')
1616
+ : decision;
983
1617
  }
984
1618
  // Evidence gathering does not decide the request's action or scope.
985
1619
  // In particular, an inherited owner must not turn a read into an
986
1620
  // immutable research preparation before the main model can interpret it.
987
- if (state.status === 'answer' && !state.nativeUnowned && execution.parent === undefined && NATIVE_EVIDENCE_READS.has(execution.name)) {
1621
+ if (state.status === 'answer' &&
1622
+ !state.nativeUnowned &&
1623
+ execution.parent === undefined &&
1624
+ NATIVE_EVIDENCE_READS.has(execution.name)) {
988
1625
  const clarification = executionClarification(state.task, state.requestIntent, state.requestIntentContext);
989
1626
  if (clarification)
990
1627
  return denied(clarification);
991
- this.#executions.set(execution, { turn: state, execute: actual.execute, evidenceReadDigest: canonicalContentHash(execution.arguments) });
1628
+ this.#executions.set(execution, {
1629
+ turn: state,
1630
+ execute: actual.execute,
1631
+ evidenceReadDigest: canonicalContentHash(execution.arguments),
1632
+ });
992
1633
  const decision = await next();
993
- return decision?.kind === 'cancel' ? denied('Native tool policy cancelled this call') : decision;
1634
+ return decision?.kind === 'cancel'
1635
+ ? denied('Native tool policy cancelled this call')
1636
+ : decision;
994
1637
  }
995
- if (state.nativeUnowned && UNOWNED_NATIVE_TOOLS.has(execution.name) && execution.parent === undefined) {
996
- if (await this.host.nativeOwnership?.(state.input) !== 'unowned') {
1638
+ if (state.nativeUnowned &&
1639
+ UNOWNED_NATIVE_TOOLS.has(execution.name) &&
1640
+ execution.parent === undefined) {
1641
+ if ((await this.host.nativeOwnership?.(state.input)) !== 'unowned') {
997
1642
  state.nativeUnowned = false;
998
1643
  return denied('Native execution acquired a Kiokuko owner; its current authority is required');
999
1644
  }
1000
1645
  this.assertCurrent(state);
1001
1646
  if (this.definition(execution)?.execute !== actual.execute)
1002
1647
  return denied('Native tool definition changed');
1003
- this.#executions.set(execution, { turn: state, execute: actual.execute, nativeUnownedDigest: canonicalContentHash(execution.arguments) });
1648
+ this.#executions.set(execution, {
1649
+ turn: state,
1650
+ execute: actual.execute,
1651
+ nativeUnownedDigest: canonicalContentHash(execution.arguments),
1652
+ });
1004
1653
  const decision = await next();
1005
- return decision?.kind === 'cancel' ? denied('Native tool policy cancelled this call') : decision;
1654
+ return decision?.kind === 'cancel'
1655
+ ? denied('Native tool policy cancelled this call')
1656
+ : decision;
1006
1657
  }
1007
1658
  const skillReader = this.#skillReaders.get(state.input.agent);
1008
- const skillRead = execution.name === 'skill' && skillReader !== undefined && actual.execute === skillReader && state.status === 'answer';
1009
- const carrier = execution.name === 'run_code' ? preparationCarrier(execution.arguments) : undefined;
1010
- const carrierDigest = carrier ? canonicalContentHash(carrier) : undefined;
1011
- if (execution.name === 'run_code' && state.status !== 'prepared' && !carrier
1012
- && !automaticPreparationType(state.intent, state.requestIntent, execution.name))
1659
+ const skillRead = execution.name === 'skill' &&
1660
+ skillReader !== undefined &&
1661
+ actual.execute === skillReader &&
1662
+ state.status === 'answer';
1663
+ const carrier = execution.name === 'run_code'
1664
+ ? preparationCarrier(execution.arguments)
1665
+ : undefined;
1666
+ const carrierDigest = carrier
1667
+ ? canonicalContentHash(carrier)
1668
+ : undefined;
1669
+ if (execution.name === 'run_code' &&
1670
+ state.status !== 'prepared' &&
1671
+ !carrier &&
1672
+ !automaticPreparationType(state.intent, state.requestIntent, execution.name))
1013
1673
  return denied('Clarify the requested work before code execution.');
1014
1674
  if (carrier) {
1015
- if (!(state.status === 'answer' || state.status === 'prepared' && this.host.ready(state.input.agent, state.input.turn)) || state.preparationCalls.has(execution.callId))
1675
+ if (!(state.status === 'answer' ||
1676
+ (state.status === 'prepared' &&
1677
+ this.host.ready(state.input.agent, state.input.turn))) ||
1678
+ state.preparationCalls.has(execution.callId))
1016
1679
  return denied('Preparation carrier is not current');
1017
1680
  }
1018
1681
  else if (execution.name === TASK_PREPARE_TOOL) {
1019
- if (actual.execute !== definition.execute || !(state.status === 'answer' || state.status === 'prepared' && this.host.ready(state.input.agent, state.input.turn)) || state.preparationCalls.has(execution.callId))
1682
+ if (actual.execute !== definition.execute ||
1683
+ !(state.status === 'answer' ||
1684
+ (state.status === 'prepared' &&
1685
+ this.host.ready(state.input.agent, state.input.turn))) ||
1686
+ state.preparationCalls.has(execution.callId))
1020
1687
  return denied('Task preparation is not current');
1021
1688
  }
1022
1689
  else if (!skillRead) {
@@ -1032,13 +1699,21 @@ export class OnDemandIntake {
1032
1699
  this.assertCurrent(state);
1033
1700
  if (this.definition(execution)?.execute !== actual.execute)
1034
1701
  return denied('Native tool definition changed during preparation');
1035
- this.#executions.set(execution, { turn: state, execute: actual.execute,
1036
- ...(carrier && carrierDigest ? { carrier, carrierDigest } : {}), ...(skillRead ? { skillRead: true } : {}),
1037
- ...(execution.name === TASK_PREPARE_TOOL ? { requestPreparation: true } : {}) });
1702
+ this.#executions.set(execution, {
1703
+ turn: state,
1704
+ execute: actual.execute,
1705
+ ...(carrier && carrierDigest ? { carrier, carrierDigest } : {}),
1706
+ ...(skillRead ? { skillRead: true } : {}),
1707
+ ...(execution.name === TASK_PREPARE_TOOL
1708
+ ? { requestPreparation: true }
1709
+ : {}),
1710
+ });
1038
1711
  // Preserve native allow/deny/ask unchanged. Approval is resolved by the native registry AFTER this waterfall.
1039
1712
  const decision = await next();
1040
1713
  // DSH 0.1.5 does not understand cancel and otherwise dispatches it without running guards.
1041
- return decision?.kind === 'cancel' ? denied('Native tool policy cancelled this call') : decision;
1714
+ return decision?.kind === 'cancel'
1715
+ ? denied('Native tool policy cancelled this call')
1716
+ : decision;
1042
1717
  }
1043
1718
  catch (error) {
1044
1719
  if (error instanceof RequestClarification) {
@@ -1047,10 +1722,14 @@ export class OnDemandIntake {
1047
1722
  return denied(`Execution was not prepared; no work was executed. ${JSON.stringify(result)}`);
1048
1723
  }
1049
1724
  catch (cancelled) {
1050
- return denied(cancelled instanceof Error ? cancelled.message : 'Clarification cancelled');
1725
+ return denied(cancelled instanceof Error
1726
+ ? cancelled.message
1727
+ : 'Clarification cancelled');
1051
1728
  }
1052
1729
  }
1053
- return denied(error instanceof Error ? error.message : 'Execution preparation failed');
1730
+ return denied(error instanceof Error
1731
+ ? error.message
1732
+ : 'Execution preparation failed');
1054
1733
  }
1055
1734
  }, { prepend: true }));
1056
1735
  this.#disposers.push(context.on('tools/execute', async (execution, next) => {
@@ -1060,21 +1739,37 @@ export class OnDemandIntake {
1060
1739
  if (control) {
1061
1740
  if (!this.isControl(execution))
1062
1741
  throw new Error('Task control identity or arguments changed');
1063
- execution.signal = AbortSignal.any([execution.signal, this.#lifecycle.signal, ...(control.turn ? [control.turn.input.signal] : [])]);
1742
+ execution.signal = AbortSignal.any([
1743
+ execution.signal,
1744
+ this.#lifecycle.signal,
1745
+ ...(control.turn ? [control.turn.input.signal] : []),
1746
+ ]);
1064
1747
  if (!control.invoke)
1065
1748
  return next();
1066
1749
  const result = await control.invoke(control.args, execution);
1067
- return { isError: false, value: { logs: ['Host task operation. No program was evaluated.'], result },
1068
- content: [{ type: 'text', text: JSON.stringify(result) }] };
1750
+ return {
1751
+ isError: false,
1752
+ value: {
1753
+ logs: ['Host task operation. No program was evaluated.'],
1754
+ result,
1755
+ },
1756
+ content: [{ type: 'text', text: JSON.stringify(result) }],
1757
+ };
1069
1758
  }
1070
1759
  const proof = this.#executions.get(execution);
1071
1760
  if (!proof)
1072
1761
  return next(); // Existing independently admitted runs keep their original path.
1073
1762
  const state = this.bound(execution);
1074
- if (!state || proof.turn !== state || this.definition(execution)?.execute !== proof.execute)
1763
+ if (!state ||
1764
+ proof.turn !== state ||
1765
+ this.definition(execution)?.execute !== proof.execute)
1075
1766
  throw new Error('On-demand dispatch identity changed');
1076
1767
  this.assertCurrent(state);
1077
- execution.signal = AbortSignal.any([execution.signal, state.input.signal, this.#lifecycle.signal]);
1768
+ execution.signal = AbortSignal.any([
1769
+ execution.signal,
1770
+ state.input.signal,
1771
+ this.#lifecycle.signal,
1772
+ ]);
1078
1773
  if (proof.nativeCommandDigest !== undefined) {
1079
1774
  if (!this.nativeCommand(execution))
1080
1775
  throw new Error('Native command identity or arguments changed');
@@ -1086,13 +1781,17 @@ export class OnDemandIntake {
1086
1781
  return withRootLispAdmission(execution, () => this.nativeLisp(execution), next);
1087
1782
  }
1088
1783
  if (proof.evidenceReadDigest !== undefined) {
1089
- if (state.status !== 'answer' || execution.parent !== undefined || !NATIVE_EVIDENCE_READS.has(execution.name)
1090
- || canonicalContentHash(execution.arguments) !== proof.evidenceReadDigest)
1784
+ if (state.status !== 'answer' ||
1785
+ execution.parent !== undefined ||
1786
+ !NATIVE_EVIDENCE_READS.has(execution.name) ||
1787
+ canonicalContentHash(execution.arguments) !==
1788
+ proof.evidenceReadDigest)
1091
1789
  throw new Error('Native evidence read identity or arguments changed');
1092
1790
  return next();
1093
1791
  }
1094
1792
  if (proof.nativeUnownedDigest !== undefined) {
1095
- if (!this.unownedNative(execution) || await this.host.nativeOwnership?.(state.input) !== 'unowned') {
1793
+ if (!this.unownedNative(execution) ||
1794
+ (await this.host.nativeOwnership?.(state.input)) !== 'unowned') {
1096
1795
  state.nativeUnowned = false;
1097
1796
  throw new Error('Native unowned authority changed or became unavailable');
1098
1797
  }
@@ -1102,21 +1801,26 @@ export class OnDemandIntake {
1102
1801
  if (!proof.carrier)
1103
1802
  return next();
1104
1803
  const carrier = preparationCarrier(execution.arguments);
1105
- if (!(state.status === 'answer' || state.status === 'prepared' && this.host.ready(state.input.agent, state.input.turn)) || state.preparationCalls.has(execution.callId)
1106
- || !carrier || canonicalContentHash(carrier) !== proof.carrierDigest)
1804
+ if (!(state.status === 'answer' ||
1805
+ (state.status === 'prepared' &&
1806
+ this.host.ready(state.input.agent, state.input.turn))) ||
1807
+ state.preparationCalls.has(execution.callId) ||
1808
+ !carrier ||
1809
+ canonicalContentHash(carrier) !== proof.carrierDigest)
1107
1810
  throw new Error('Preparation carrier was already consumed or changed');
1108
1811
  state.preparationCalls.add(execution.callId);
1109
1812
  try {
1110
- const requestIntent = this.bindIntent(state, carrier.requestIntent);
1813
+ const requestIntent = await this.bindIntent(state, carrier.requestIntent);
1111
1814
  const clarification = executionClarification(state.task, requestIntent, state.requestIntentContext);
1112
1815
  if (clarification)
1113
1816
  throw new RequestClarification(clarification);
1114
- const unresolved = requestIntent !== undefined && requestIntentIsUnresolved(requestIntent);
1115
- const legacyTaskTypeAllowed = carrier.taskType !== undefined && !state.requestIntentContext?.activeGoal && !state.requestIntentContext?.activeTask
1116
- && (state.requestIntentContext?.proposals.length ?? 0) === 0;
1117
- if (unresolved && !legacyTaskTypeAllowed)
1817
+ const unresolved = requestIntent !== undefined &&
1818
+ requestIntentIsUnresolved(requestIntent);
1819
+ if (unresolved)
1118
1820
  throw new RequestClarification('Clarify the requested action and scope before preparing work');
1119
- const intentType = unresolved ? undefined : requestIntentTaskType(requestIntent, state.taskType ?? state.intent.taskType ?? carrier.taskType);
1821
+ const intentType = unresolved
1822
+ ? undefined
1823
+ : requestIntentTaskType(requestIntent, state.taskType ?? state.intent.taskType ?? carrier.taskType);
1120
1824
  const taskType = intentType ?? carrier.taskType;
1121
1825
  if (carrier.taskType !== undefined && taskType !== carrier.taskType)
1122
1826
  throw new Error('The advisory taskType conflicts with the bound request action');
@@ -1125,20 +1829,50 @@ export class OnDemandIntake {
1125
1829
  }
1126
1830
  state.memoryOwner = memoryOwnerKind(requestIntent, taskType);
1127
1831
  await this.prepare(state, taskType, execution.signal, requestIntent, carrier.requestIntent !== undefined);
1128
- const result = { prepared: true, taskType: state.taskType, originalTask: state.task,
1129
- ...(state.requestIntent ? { requestIntent: state.requestIntent } : {}),
1130
- ...(state.requestIntent && requestIntentIsUnresolved(state.requestIntent) ? { requestInterpretation: requestInterpretationRecovery(state.requestIntent, state.requestIntentContext, true) } : {}),
1131
- memoryOwner: state.memoryOwner, permissions: 'unchanged_native_policy', programExecuted: false };
1132
- return { isError: false, value: { logs: ['Host intake prepared. No program was evaluated.'], result }, content: [{ type: 'text', text: JSON.stringify(result) }] };
1832
+ const result = {
1833
+ prepared: true,
1834
+ taskType: state.taskType,
1835
+ originalTask: state.task,
1836
+ ...(state.requestIntent
1837
+ ? { requestIntent: state.requestIntent }
1838
+ : {}),
1839
+ ...(state.requestIntent &&
1840
+ requestIntentIsUnresolved(state.requestIntent)
1841
+ ? {
1842
+ requestInterpretation: requestInterpretationRecovery(state.requestIntent, state.requestIntentContext, true),
1843
+ }
1844
+ : {}),
1845
+ memoryOwner: state.memoryOwner,
1846
+ permissions: 'unchanged_native_policy',
1847
+ programExecuted: false,
1848
+ };
1849
+ return {
1850
+ isError: false,
1851
+ value: {
1852
+ logs: ['Host intake prepared. No program was evaluated.'],
1853
+ result,
1854
+ },
1855
+ content: [{ type: 'text', text: JSON.stringify(result) }],
1856
+ };
1133
1857
  }
1134
1858
  catch (error) {
1135
1859
  if (!(error instanceof RequestClarification))
1136
1860
  throw error;
1137
- const result = { ...await this.clarify(state, execution.signal, error), programExecuted: false };
1138
- return { isError: false, value: { logs: ['Execution was not prepared. No program was evaluated.'], result }, content: [{ type: 'text', text: JSON.stringify(result) }] };
1861
+ const result = {
1862
+ ...(await this.clarify(state, execution.signal, error)),
1863
+ programExecuted: false,
1864
+ };
1865
+ return {
1866
+ isError: false,
1867
+ value: {
1868
+ logs: ['Execution was not prepared. No program was evaluated.'],
1869
+ result,
1870
+ },
1871
+ content: [{ type: 'text', text: JSON.stringify(result) }],
1872
+ };
1139
1873
  }
1140
1874
  }, { prepend: true }));
1141
- this.#disposers.push(tools.guard(execution => {
1875
+ this.#disposers.push(tools.guard((execution) => {
1142
1876
  if (this.#stopped || execution.signal.aborted)
1143
1877
  return 'On-demand intake is stopped or cancelled';
1144
1878
  if (execution.agent && this.host.nativeChild?.(execution.agent))
@@ -1149,39 +1883,75 @@ export class OnDemandIntake {
1149
1883
  if (!actual)
1150
1884
  return 'Unknown native tool definition';
1151
1885
  if (!state) {
1152
- if (execution.agent?.session && this.#turns.has(execution.agent.session.id))
1886
+ if (execution.agent?.session &&
1887
+ this.#turns.has(execution.agent.session.id))
1153
1888
  return 'Stale on-demand native turn';
1154
1889
  const turn = this.existingTurn(execution);
1155
- return execution.agent && turn !== undefined && this.host.ready(execution.agent, turn) ? undefined : 'No current native request for tool execution';
1890
+ return execution.agent &&
1891
+ turn !== undefined &&
1892
+ this.host.ready(execution.agent, turn)
1893
+ ? undefined
1894
+ : 'No current native request for tool execution';
1156
1895
  }
1157
1896
  const proof = this.#executions.get(execution);
1158
- if (!proof || proof.turn !== state || proof.execute !== actual.execute || ['closed', 'stale', 'capturing'].includes(state.status))
1897
+ if (!proof ||
1898
+ proof.turn !== state ||
1899
+ proof.execute !== actual.execute ||
1900
+ ['closed', 'stale', 'capturing'].includes(state.status))
1159
1901
  return 'Tool execution has no current on-demand preparation';
1160
1902
  if (proof.nativeCommandDigest !== undefined)
1161
- return this.nativeCommand(execution) ? undefined : 'Native command identity or arguments changed';
1903
+ return this.nativeCommand(execution)
1904
+ ? undefined
1905
+ : 'Native command identity or arguments changed';
1162
1906
  if (proof.nativeLispDigest !== undefined)
1163
- return this.nativeLisp(execution) ? undefined : 'Native Lisp identity or arguments changed';
1907
+ return this.nativeLisp(execution)
1908
+ ? undefined
1909
+ : 'Native Lisp identity or arguments changed';
1164
1910
  if (proof.evidenceReadDigest !== undefined)
1165
- return state.status === 'answer' && execution.parent === undefined
1166
- && NATIVE_EVIDENCE_READS.has(execution.name) && canonicalContentHash(execution.arguments) === proof.evidenceReadDigest
1167
- ? undefined : 'Native evidence read identity or arguments changed';
1911
+ return state.status === 'answer' &&
1912
+ execution.parent === undefined &&
1913
+ NATIVE_EVIDENCE_READS.has(execution.name) &&
1914
+ canonicalContentHash(execution.arguments) ===
1915
+ proof.evidenceReadDigest
1916
+ ? undefined
1917
+ : 'Native evidence read identity or arguments changed';
1168
1918
  if (proof.nativeUnownedDigest !== undefined)
1169
- return this.unownedNative(execution) ? undefined : 'Native unowned execution proof changed';
1919
+ return this.unownedNative(execution)
1920
+ ? undefined
1921
+ : 'Native unowned execution proof changed';
1170
1922
  if (proof.carrier)
1171
- return (state.status === 'answer' || state.status === 'prepared' && this.host.ready(state.input.agent, state.input.turn))
1172
- && canonicalContentHash(preparationCarrier(execution.arguments) ?? null) === proof.carrierDigest ? undefined : 'Preparation carrier is not current';
1923
+ return (state.status === 'answer' ||
1924
+ (state.status === 'prepared' &&
1925
+ this.host.ready(state.input.agent, state.input.turn))) &&
1926
+ canonicalContentHash(preparationCarrier(execution.arguments) ?? null) === proof.carrierDigest
1927
+ ? undefined
1928
+ : 'Preparation carrier is not current';
1173
1929
  if (proof.skillRead)
1174
- return state.status === 'answer' && actual.execute === this.#skillReaders.get(state.input.agent) ? undefined : 'Conversational Skill read is not current';
1930
+ return state.status === 'answer' &&
1931
+ actual.execute === this.#skillReaders.get(state.input.agent)
1932
+ ? undefined
1933
+ : 'Conversational Skill read is not current';
1175
1934
  if (execution.name === TASK_PREPARE_TOOL)
1176
- return actual.execute === definition.execute && (state.status === 'answer' || state.status === 'prepared' && this.host.ready(state.input.agent, state.input.turn)) ? undefined : 'Task preparation is not current';
1177
- return state.status === 'prepared' && this.host.ready(state.input.agent, state.input.turn) ? undefined : 'Execution task is not prepared';
1935
+ return actual.execute === definition.execute &&
1936
+ (state.status === 'answer' ||
1937
+ (state.status === 'prepared' &&
1938
+ this.host.ready(state.input.agent, state.input.turn)))
1939
+ ? undefined
1940
+ : 'Task preparation is not current';
1941
+ return state.status === 'prepared' &&
1942
+ this.host.ready(state.input.agent, state.input.turn)
1943
+ ? undefined
1944
+ : 'Execution task is not prepared';
1178
1945
  }));
1179
1946
  }
1180
1947
  answerOwner(session) {
1181
1948
  if (this.#stopped)
1182
1949
  return undefined;
1183
1950
  const state = this.#turns.get(session.id);
1184
- if (!state || state.status !== 'answer' || state.input.agent.session !== session || state.memoryOwner !== 'conversation')
1951
+ if (!state ||
1952
+ state.status !== 'answer' ||
1953
+ state.input.agent.session !== session ||
1954
+ state.memoryOwner !== 'conversation')
1185
1955
  return undefined;
1186
1956
  const owner = this.host.answerContext?.owner(session, this.ownerIdentity(state));
1187
1957
  return state.input.agent === owner?.agent ? owner : undefined;
@@ -1189,17 +1959,28 @@ export class OnDemandIntake {
1189
1959
  retire(agent) {
1190
1960
  this.host.answerContext?.retire(agent);
1191
1961
  const state = agent.session && this.#turns.get(agent.session.id);
1192
- if (state?.input.agent !== agent || state.input.agent.session !== agent.session)
1962
+ if (state?.input.agent !== agent ||
1963
+ state.input.agent.session !== agent.session)
1193
1964
  return;
1194
1965
  state.controller.abort(new Error('Native agent disposed'));
1195
1966
  state.status = 'closed';
1196
1967
  this.#turns.delete(agent.session.id);
1197
1968
  this.#clarifications.delete(agent.session.id);
1198
1969
  }
1199
- stop() { this.#clarifications.clear(); this.#stopped = true; this.#lifecycle.abort(new Error('On-demand intake stopped')); }
1200
- async drain() { while (this.#pending.size)
1201
- await Promise.allSettled([...this.#pending]); }
1202
- async dispose() { this.stop(); await this.drain(); for (const dispose of this.#disposers.reverse())
1203
- dispose(); }
1970
+ stop() {
1971
+ this.#clarifications.clear();
1972
+ this.#stopped = true;
1973
+ this.#lifecycle.abort(new Error('On-demand intake stopped'));
1974
+ }
1975
+ async drain() {
1976
+ while (this.#pending.size)
1977
+ await Promise.allSettled([...this.#pending]);
1978
+ }
1979
+ async dispose() {
1980
+ this.stop();
1981
+ await this.drain();
1982
+ for (const dispose of this.#disposers.reverse())
1983
+ dispose();
1984
+ }
1204
1985
  }
1205
1986
  //# sourceMappingURL=on-demand-intake.js.map