kiokuko-dsh 0.2.28 → 0.2.30

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