kiokuko-dsh 0.2.37 → 0.2.39

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 (129) hide show
  1. package/README.ja.md +28 -0
  2. package/README.ko.md +26 -0
  3. package/README.md +28 -0
  4. package/README.zh-CN.md +26 -0
  5. package/dist/client.cjs +97 -5
  6. package/dist/client.d.ts.map +1 -1
  7. package/dist/dsh/answer-review/contracts.js +1 -1
  8. package/dist/dsh/answer-review/contracts.js.map +1 -1
  9. package/dist/dsh/completion-report.d.ts +29 -1
  10. package/dist/dsh/completion-report.d.ts.map +1 -1
  11. package/dist/dsh/completion-report.js +58 -14
  12. package/dist/dsh/completion-report.js.map +1 -1
  13. package/dist/dsh/config.d.ts +12 -0
  14. package/dist/dsh/config.d.ts.map +1 -1
  15. package/dist/dsh/config.js +3 -0
  16. package/dist/dsh/config.js.map +1 -1
  17. package/dist/dsh/core/host.d.ts +12 -0
  18. package/dist/dsh/core/host.d.ts.map +1 -1
  19. package/dist/dsh/core/host.js +8 -4
  20. package/dist/dsh/core/host.js.map +1 -1
  21. package/dist/dsh/core/index.d.ts +32 -0
  22. package/dist/dsh/core/index.d.ts.map +1 -1
  23. package/dist/dsh/decisions/diagnostics.d.ts +4 -3
  24. package/dist/dsh/decisions/diagnostics.d.ts.map +1 -1
  25. package/dist/dsh/decisions/diagnostics.js +2 -2
  26. package/dist/dsh/decisions/diagnostics.js.map +1 -1
  27. package/dist/dsh/decisions/host.d.ts.map +1 -1
  28. package/dist/dsh/decisions/host.js +5 -0
  29. package/dist/dsh/decisions/host.js.map +1 -1
  30. package/dist/dsh/decisions/intent-assessment.d.ts +24 -3
  31. package/dist/dsh/decisions/intent-assessment.d.ts.map +1 -1
  32. package/dist/dsh/decisions/intent-assessment.js +36 -10
  33. package/dist/dsh/decisions/intent-assessment.js.map +1 -1
  34. package/dist/dsh/decisions/openai.d.ts.map +1 -1
  35. package/dist/dsh/decisions/openai.js +3 -0
  36. package/dist/dsh/decisions/openai.js.map +1 -1
  37. package/dist/dsh/decisions/service.d.ts +2 -0
  38. package/dist/dsh/decisions/service.d.ts.map +1 -1
  39. package/dist/dsh/decisions/service.js +9 -0
  40. package/dist/dsh/decisions/service.js.map +1 -1
  41. package/dist/dsh/guardrail/contracts.d.ts +1 -1
  42. package/dist/dsh/guardrail/contracts.d.ts.map +1 -1
  43. package/dist/dsh/guardrail/evidence.d.ts.map +1 -1
  44. package/dist/dsh/guardrail/evidence.js +4 -0
  45. package/dist/dsh/guardrail/evidence.js.map +1 -1
  46. package/dist/dsh/host-adapter.d.ts +2 -0
  47. package/dist/dsh/host-adapter.d.ts.map +1 -1
  48. package/dist/dsh/host-adapter.js +9 -5
  49. package/dist/dsh/host-adapter.js.map +1 -1
  50. package/dist/dsh/index.d.ts +10 -2
  51. package/dist/dsh/index.d.ts.map +1 -1
  52. package/dist/dsh/index.js +6 -3
  53. package/dist/dsh/index.js.map +1 -1
  54. package/dist/dsh/lisp/host-tool-dispatch.d.ts +2 -0
  55. package/dist/dsh/lisp/host-tool-dispatch.d.ts.map +1 -1
  56. package/dist/dsh/lisp/host-tool-dispatch.js +9 -1
  57. package/dist/dsh/lisp/host-tool-dispatch.js.map +1 -1
  58. package/dist/dsh/lisp/http.d.ts.map +1 -1
  59. package/dist/dsh/lisp/http.js +13 -1
  60. package/dist/dsh/lisp/http.js.map +1 -1
  61. package/dist/dsh/lisp/manager.d.ts +4 -0
  62. package/dist/dsh/lisp/manager.d.ts.map +1 -1
  63. package/dist/dsh/lisp/manager.js +35 -9
  64. package/dist/dsh/lisp/manager.js.map +1 -1
  65. package/dist/dsh/lisp/native-stop-receipt.d.ts +25 -0
  66. package/dist/dsh/lisp/native-stop-receipt.d.ts.map +1 -0
  67. package/dist/dsh/lisp/native-stop-receipt.js +22 -0
  68. package/dist/dsh/lisp/native-stop-receipt.js.map +1 -0
  69. package/dist/dsh/lisp/proposal-batch.d.ts.map +1 -1
  70. package/dist/dsh/lisp/proposal-batch.js +2 -1
  71. package/dist/dsh/lisp/proposal-batch.js.map +1 -1
  72. package/dist/dsh/lisp/sandbox.d.ts +1 -0
  73. package/dist/dsh/lisp/sandbox.d.ts.map +1 -1
  74. package/dist/dsh/lisp/sandbox.js +2 -2
  75. package/dist/dsh/lisp/sandbox.js.map +1 -1
  76. package/dist/dsh/lisp/status-text.d.ts +34 -0
  77. package/dist/dsh/lisp/status-text.d.ts.map +1 -0
  78. package/dist/dsh/lisp/status-text.js +65 -0
  79. package/dist/dsh/lisp/status-text.js.map +1 -0
  80. package/dist/dsh/lisp/surface.d.ts.map +1 -1
  81. package/dist/dsh/lisp/surface.js +44 -10
  82. package/dist/dsh/lisp/surface.js.map +1 -1
  83. package/dist/dsh/live-config.d.ts +49 -0
  84. package/dist/dsh/live-config.d.ts.map +1 -0
  85. package/dist/dsh/live-config.js +77 -0
  86. package/dist/dsh/live-config.js.map +1 -0
  87. package/dist/dsh/managed-file-deployment.d.ts +6 -0
  88. package/dist/dsh/managed-file-deployment.d.ts.map +1 -1
  89. package/dist/dsh/managed-file-deployment.js +17 -1
  90. package/dist/dsh/managed-file-deployment.js.map +1 -1
  91. package/dist/dsh/managed-operation-journal.d.ts +5 -1
  92. package/dist/dsh/managed-operation-journal.d.ts.map +1 -1
  93. package/dist/dsh/managed-operation-journal.js +17 -4
  94. package/dist/dsh/managed-operation-journal.js.map +1 -1
  95. package/dist/dsh/on-demand-intake.d.ts +21 -4
  96. package/dist/dsh/on-demand-intake.d.ts.map +1 -1
  97. package/dist/dsh/on-demand-intake.js +167 -55
  98. package/dist/dsh/on-demand-intake.js.map +1 -1
  99. package/dist/dsh/setup.d.ts.map +1 -1
  100. package/dist/dsh/setup.js +8 -2
  101. package/dist/dsh/setup.js.map +1 -1
  102. package/dist/dsh/startup-defaults-settings.d.ts +20 -0
  103. package/dist/dsh/startup-defaults-settings.d.ts.map +1 -0
  104. package/dist/dsh/startup-defaults-settings.js +27 -0
  105. package/dist/dsh/startup-defaults-settings.js.map +1 -0
  106. package/dist/dsh/startup-defaults.d.ts +41 -0
  107. package/dist/dsh/startup-defaults.d.ts.map +1 -0
  108. package/dist/dsh/startup-defaults.js +29 -0
  109. package/dist/dsh/startup-defaults.js.map +1 -0
  110. package/dist/dsh/task-startup.d.ts +7 -0
  111. package/dist/dsh/task-startup.d.ts.map +1 -1
  112. package/dist/dsh/task-startup.js +30 -9
  113. package/dist/dsh/task-startup.js.map +1 -1
  114. package/dist/enno-oduno/verifier.d.ts.map +1 -1
  115. package/dist/enno-oduno/verifier.js +5 -1
  116. package/dist/enno-oduno/verifier.js.map +1 -1
  117. package/dist/locale/en.json +1 -1
  118. package/dist/locale/ja.json +1 -1
  119. package/dist/locale/ko.json +1 -1
  120. package/dist/locale/zh-CN.json +1 -1
  121. package/docs/README.ja.md +1 -0
  122. package/docs/README.md +1 -0
  123. package/docs/answer-review.md +8 -0
  124. package/docs/lisp.md +45 -4
  125. package/docs/micro-judge.md +1 -1
  126. package/docs/on-demand-intake.md +2 -0
  127. package/docs/typed-decisions.md +13 -3
  128. package/lisp/supervisor.mjs +90 -9
  129. package/package.json +1 -1
@@ -17,6 +17,7 @@ import { KIOKUKO_DSH_SOURCE_KIND, isKiokukoDshSource } from './plugin-source.js'
17
17
  import { taskAllowedOperations, taskCanExecute, } from './task-state.js';
18
18
  import { createRequestIntentContext, unresolvedRequestIntent, validateMainModelRequestIntent, requestIntentReferenceIds, requestIntentRequiredReferenceIds, requestIntentIsUnresolved, requestIntentMessageRefs, requestIntentTaskType, RequestIntentSubmissionSchema, } from './decisions/request-intent.js';
19
19
  import { memoryOwnerKind, } from './memory-owner.js';
20
+ import { assessmentAllowsPreparation, mayConfirmRequestInterpretation } from './decisions/intent-assessment.js';
20
21
  class RequestClarification extends Error {
21
22
  assessment;
22
23
  interpretation;
@@ -195,6 +196,7 @@ export class OnDemandIntake {
195
196
  }
196
197
  }
197
198
  #turns = new Map();
199
+ #nativeConfirmations = new WeakMap();
198
200
  #clarificationContexts = new WeakMap();
199
201
  #clarifications = new Map();
200
202
  #executions = new WeakMap();
@@ -249,20 +251,20 @@ export class OnDemandIntake {
249
251
  let verified;
250
252
  state.auxiliaryReceipt = await this.host.recovery?.defer({ source: { sessionId: state.input.agent.session.id, turn: state.input.turn,
251
253
  origin: 'intent-assessment', sourceDigest: canonicalContentHash(state.task), contextDigest: digest },
252
- current: () => this.recoveryCurrent(state) && state.assessment?.digest === digest && canonicalContentHash([state.requestIntentContext, decision]) === digest,
254
+ current: () => this.recoveryCurrent(state) && !this.#clarifications.has(state.session.id) && state.assessment?.digest === digest && canonicalContentHash([state.requestIntentContext, decision]) === digest,
253
255
  check: async (signal, attempt) => {
254
256
  await this.host.validate({ ...state.input, signal });
255
- const result = await this.host.assess?.({ ...state.input, signal }, context, decision, attempt);
257
+ const result = await this.host.assess?.({ ...state.input, signal }, context, decision, attempt, this.hasNativeConfirmation(state, context, decision));
256
258
  signal.throwIfAborted();
257
259
  if (!result)
258
260
  return { state: 'rejected', reason: 'assessment_disabled' };
259
- if (result.outcome === 'consistent') {
261
+ if (assessmentAllowsPreparation(result)) {
260
262
  verified = result;
261
- return { state: 'verified', reason: 'semantic_rechecked' };
263
+ return { state: 'verified', reason: result.outcome === 'user_confirmed' ? 'native_user_confirmation_accepted' : 'semantic_rechecked' };
262
264
  }
263
265
  return { state: result.semantic === 'contradicted' ? 'rejected' : 'retry_wait', reason: result.semantic === 'contradicted' ? 'semantic_contradicted' : 'assessment_unresolved' };
264
266
  }, verified: async () => {
265
- if (verified && this.recoveryCurrent(state) && state.assessment?.digest === digest && canonicalContentHash([state.requestIntentContext, decision]) === digest) {
267
+ if (verified && this.recoveryCurrent(state) && !this.#clarifications.has(state.session.id) && state.assessment?.digest === digest && canonicalContentHash([state.requestIntentContext, decision]) === digest) {
266
268
  state.assessment = { digest, result: verified };
267
269
  await this.diagnose(state, { kind: 'assessment', assessment: verified });
268
270
  }
@@ -357,6 +359,11 @@ export class OnDemandIntake {
357
359
  void promise.then(() => this.#pending.delete(promise), () => this.#pending.delete(promise));
358
360
  return promise;
359
361
  }
362
+ /** True only while the previous turn has no matching turn/end in the session log. */
363
+ turnIsOpen(state) {
364
+ const events = state.input.agent.session?.snapshotEvents?.() ?? [];
365
+ return !events.some((event) => event.type === 'turn/end' && eventTurn(event) === state.input.turn);
366
+ }
360
367
  existingTurn(execution) {
361
368
  const last = execution.agent?.session
362
369
  ?.snapshotEvents?.()
@@ -395,7 +402,7 @@ export class OnDemandIntake {
395
402
  messageRefs: requestIntentMessageRefs(input.messages, task),
396
403
  });
397
404
  try {
398
- await this.host.prepareNativeCoding(input, task, context);
405
+ await this.host.prepareNativeCoding(input, task, context, this.nativeContinuation(input));
399
406
  }
400
407
  catch (error) {
401
408
  input.signal.throwIfAborted();
@@ -463,8 +470,12 @@ export class OnDemandIntake {
463
470
  return false;
464
471
  }
465
472
  }
473
+ // A superseded capture is only a live turn while its own boundary stays open.
474
+ // Once turn/end exists for it, the host has finished that turn; its failure
475
+ // must not refuse every later human request for the rest of the session.
466
476
  if (previous.input.turn !== input.turn &&
467
- !['closed', 'prepared'].includes(previous.status))
477
+ !['closed', 'prepared'].includes(previous.status) &&
478
+ this.turnIsOpen(previous))
468
479
  throw new Error('Previous on-demand turn has not ended');
469
480
  previous.controller.abort(new Error('A newer native turn superseded this request'));
470
481
  }
@@ -486,6 +497,8 @@ export class OnDemandIntake {
486
497
  messages: Object.freeze(structuredClone([...input.messages])),
487
498
  },
488
499
  task: humanTask(input.messages),
500
+ ...(this.isClarificationContinuation(input)
501
+ ? { clarificationPending: true } : {}),
489
502
  humanContents: new Map(humanMessages(input.messages).map((message) => [
490
503
  humanId(message),
491
504
  canonicalContentHash(message),
@@ -529,7 +542,7 @@ export class OnDemandIntake {
529
542
  messageRefs: requestIntentMessageRefs(state.input.messages, state.task),
530
543
  });
531
544
  this.assertCurrent(state);
532
- await this.host.prepareNativeCoding?.(state.input, state.task, context);
545
+ await this.host.prepareNativeCoding?.(state.input, state.task, context, this.nativeContinuation(state.input));
533
546
  this.assertCurrent(state);
534
547
  state.nativeUnowned = true;
535
548
  if (context) {
@@ -546,7 +559,7 @@ export class OnDemandIntake {
546
559
  const context = await this.host.requestContext?.(state.input, state.task)
547
560
  ?? createRequestIntentContext({ task: state.task, messageRefs: requestIntentMessageRefs(state.input.messages, state.task) });
548
561
  this.assertCurrent(state);
549
- await this.host.prepareNativeCoding(state.input, state.task, context);
562
+ await this.host.prepareNativeCoding(state.input, state.task, context, this.nativeContinuation(state.input));
550
563
  this.assertCurrent(state);
551
564
  }
552
565
  try {
@@ -778,6 +791,55 @@ export class OnDemandIntake {
778
791
  current,
779
792
  });
780
793
  }
794
+ isClarificationContinuation(input) {
795
+ const messages = humanMessages(input.messages), known = this.#clarificationContexts.get(input.agent);
796
+ if (!known)
797
+ return false;
798
+ const sources = collectClarificationContext(messages.map(message => ({ id: humanId(message), text: humanTask([message]) })), known).sources;
799
+ return messages.some(message => {
800
+ const reply = known.get(humanId(message));
801
+ return reply && canonicalContentHash(reply.sources) === canonicalContentHash(sources);
802
+ });
803
+ }
804
+ /** Startup preferences are reused only from this exact native confirmation,
805
+ * never by matching a model-written "continue" or quoted answer. */
806
+ nativeContinuation(input) {
807
+ const messages = humanMessages(input.messages);
808
+ const clarification = collectClarificationContext(messages.map(message => ({ id: humanId(message), text: humanTask([message]) })), this.#clarificationContexts.get(input.agent) ?? new Map());
809
+ for (const message of messages) {
810
+ const receipt = this.#nativeConfirmations.get(input.agent)?.get(humanId(message));
811
+ if (receipt && receipt.session === input.agent.session && receipt.originalTurn < input.turn &&
812
+ receipt.contentDigest === canonicalContentHash(humanTask([message])) &&
813
+ receipt.sourcesDigest === canonicalContentHash(clarification.sources))
814
+ return { turn: receipt.originalTurn, task: receipt.originalTask };
815
+ }
816
+ return undefined;
817
+ }
818
+ confirmationScope(context) {
819
+ // Eligible confirmations adopt no proposals. Fresh unselected assistant
820
+ // prose must not invalidate the human answer; selected refs are bound in
821
+ // confirmationCandidate and still cannot enter this path.
822
+ return canonicalContentHash({ activeTask: context.activeTask ?? null,
823
+ activeGoal: context.activeGoal ?? null });
824
+ }
825
+ confirmationCandidate(candidate) {
826
+ return canonicalContentHash([candidate.relation, candidate.action, candidate.changeScope,
827
+ candidate.taskOperation ?? null, candidate.evidence.proposalRefs]);
828
+ }
829
+ hasNativeConfirmation(state, context, candidate) {
830
+ if (!mayConfirmRequestInterpretation(candidate))
831
+ return false;
832
+ const messages = humanMessages(state.input.messages);
833
+ const clarification = collectClarificationContext(messages.map(message => ({ id: humanId(message), text: humanTask([message]) })), this.#clarificationContexts.get(state.input.agent) ?? new Map());
834
+ return messages.some(message => {
835
+ const receipt = this.#nativeConfirmations.get(state.input.agent)?.get(humanId(message));
836
+ return !!receipt && receipt.session === state.input.agent.session &&
837
+ receipt.contentDigest === canonicalContentHash(humanTask([message])) &&
838
+ receipt.sourcesDigest === canonicalContentHash(clarification.sources) &&
839
+ receipt.scopeDigest === this.confirmationScope(context) &&
840
+ receipt.candidateDigest === this.confirmationCandidate(candidate);
841
+ });
842
+ }
781
843
  async assess(state, decision) {
782
844
  if (!this.host.assess ||
783
845
  decision?.action === 'answer' ||
@@ -793,7 +855,7 @@ export class OnDemandIntake {
793
855
  const started = performance.now(), deadline = AbortSignal.timeout(1500);
794
856
  const signal = AbortSignal.any([state.input.signal, deadline]);
795
857
  try {
796
- result = await abortable(this.host.assess({ ...state.input, signal }, context, decision), signal);
858
+ result = await abortable(this.host.assess({ ...state.input, signal }, context, decision, undefined, this.hasNativeConfirmation(state, context, decision)), signal);
797
859
  }
798
860
  catch {
799
861
  state.input.signal.throwIfAborted();
@@ -809,10 +871,10 @@ export class OnDemandIntake {
809
871
  throw new Error('Assessment source changed');
810
872
  state.assessment = { digest, result };
811
873
  await this.diagnose(state, { kind: 'assessment', assessment: result });
812
- if (result.outcome !== 'consistent')
874
+ if (!assessmentAllowsPreparation(result))
813
875
  await this.deferAssessment(state, context, decision, digest);
814
876
  }
815
- return state.assessment.result.outcome === 'consistent';
877
+ return assessmentAllowsPreparation(state.assessment.result);
816
878
  }
817
879
  async prepare(state, taskType, signal, requestIntent, explicitIntent = false) {
818
880
  this.assertCurrent(state);
@@ -900,6 +962,7 @@ export class OnDemandIntake {
900
962
  requestIntent: state.requestIntent ?? null,
901
963
  });
902
964
  state.status = 'prepared';
965
+ delete state.clarificationPending;
903
966
  this.#clarifications.delete(state.input.agent.session.id);
904
967
  delete state.confirmationResume;
905
968
  })();
@@ -987,17 +1050,22 @@ export class OnDemandIntake {
987
1050
  reason.message.startsWith('The requested change scope');
988
1051
  const target = !!executionClarification(state.task, decision, context);
989
1052
  const transition = context?.activeTask && !taskCanExecute(context.activeTask);
990
- const question = reason.assessment
991
- ? '現在の依頼と実行候補の対象・操作・制約を照合できませんでした。今回の対象と希望する操作、以前の指示を変更する範囲を教えてください。'
992
- : transition
993
- ? `現在のタスクは ${context.activeTask.state} です。続けるために、どの操作を希望しますか?(${taskAllowedOperations(context.activeTask).join(' / ')})`
994
- : target
995
- ? 'どの具体的なファイル・項目・相手・操作を対象にしますか?'
996
- : scope
997
- ? '変更するのは文章だけですか、それともコード・テスト・設定も含みますか?'
998
- : decision?.action === 'unknown'
999
- ? 'その対象に、具体的に何をしてほしいですか?'
1000
- : null;
1053
+ const confirmCurrent = reason.assessment && state.assessment?.result.semantic === 'insufficient' &&
1054
+ !!decision && mayConfirmRequestInterpretation(decision) && !(decision.relation === 'same_scope_continuation' && context?.proposals.length) && !target && !scope && !transition;
1055
+ const confirmLabel = 'この依頼の対象・操作・制約のまま進める';
1056
+ const question = confirmCurrent
1057
+ ? `次の依頼について、対象と操作を判定しきれませんでした。\n\n${state.task}\n\n予定している操作: ${{ answer: '回答', research: '調査', verify: '検証', change: decision.changeScope === 'document' ? '文書の編集' : 'コード・テスト・設定の変更', operate: '操作', unknown: '未確定' }[decision.action]}。\nこの依頼に書かれた対象・操作・制約のまま進めてよいですか?違う場合は、具体的な対象と希望する操作を訂正してください。`
1058
+ : reason.assessment
1059
+ ? target ? '依頼の対象を特定できませんでした。変更・確認する具体的なファイルや項目と、希望する操作を教えてください。' : '依頼の対象・操作・制約を判定しきれませんでした。今回の具体的な対象と希望する操作、以前の指示を変更する範囲を教えてください。'
1060
+ : transition
1061
+ ? `現在のタスクは ${context.activeTask.state} です。続けるために、どの操作を希望しますか?(${taskAllowedOperations(context.activeTask).join(' / ')})`
1062
+ : target
1063
+ ? 'どの具体的なファイル・項目・相手・操作を対象にしますか?'
1064
+ : scope
1065
+ ? '変更するのは文章だけですか、それともコード・テスト・設定も含みますか?'
1066
+ : decision?.action === 'unknown'
1067
+ ? 'その対象に、具体的に何をしてほしいですか?'
1068
+ : null;
1001
1069
  // Internal relationship classification belongs to the model. An unresolved
1002
1070
  // classifier is not evidence that the human omitted an action or target.
1003
1071
  if (reason.interpretation || question === null)
@@ -1020,6 +1088,7 @@ export class OnDemandIntake {
1020
1088
  prepared: false,
1021
1089
  status: 'assessment_unavailable',
1022
1090
  semantic: a.semantic,
1091
+ semanticChecks: a.semanticChecks ?? [],
1023
1092
  detector: a.detector.state === 'unavailable'
1024
1093
  ? a.detector.reason
1025
1094
  : a.detector.result.status,
@@ -1031,27 +1100,6 @@ export class OnDemandIntake {
1031
1100
  detail: '原文照合または有限領域検査を利用できません。実行は準備されていません。',
1032
1101
  };
1033
1102
  }
1034
- // An inconclusive detector is not evidence that the human omitted a target.
1035
- // Return the submitted interpretation for repair; keep preparation denied.
1036
- if (reason.assessment &&
1037
- state.assessment?.result.semantic === 'insufficient' &&
1038
- reason.candidate) {
1039
- return {
1040
- prepared: false,
1041
- status: 'assessment_rejected',
1042
- semantic: state.assessment.result.semantic,
1043
- semanticIssues: state.assessment.result.semanticIssues ?? [],
1044
- originalTask: state.task,
1045
- requestIntent: reason.candidate,
1046
- currentReferences: context
1047
- ? requestIntentRequiredReferenceIds(context)
1048
- : [],
1049
- availableReferences: context ? requestIntentReferenceIds(context) : [],
1050
- permissions: 'unchanged_native_policy',
1051
- nextAction: 'revise_request_intent_or_report_conflict',
1052
- 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.',
1053
- };
1054
- }
1055
1103
  const replies = this.#clarificationContexts.get(state.input.agent) ??
1056
1104
  new Map();
1057
1105
  const clarification = collectClarificationContext(humanMessages(state.input.messages).map((message) => ({
@@ -1072,6 +1120,7 @@ export class OnDemandIntake {
1072
1120
  status: 'assessment_rejected',
1073
1121
  semantic: state.assessment?.result.semantic,
1074
1122
  semanticIssues: state.assessment?.result.semanticIssues ?? [],
1123
+ semanticChecks: state.assessment?.result.semanticChecks ?? [],
1075
1124
  detector: state.assessment?.result.detector.state === 'available'
1076
1125
  ? state.assessment.result.detector.result.status
1077
1126
  : state.assessment?.result.detector.reason,
@@ -1124,6 +1173,7 @@ export class OnDemandIntake {
1124
1173
  ? {
1125
1174
  semantic: state.assessment?.result.semantic,
1126
1175
  semanticIssues: state.assessment?.result.semanticIssues ?? [],
1176
+ semanticChecks: state.assessment?.result.semanticChecks ?? [],
1127
1177
  }
1128
1178
  : {}),
1129
1179
  permissions: 'unchanged_native_policy',
@@ -1137,6 +1187,7 @@ export class OnDemandIntake {
1137
1187
  result: result,
1138
1188
  operation: undefined,
1139
1189
  };
1190
+ state.clarificationPending = true;
1140
1191
  this.#clarifications.set(sessionId, pending);
1141
1192
  await this.diagnose(state, {
1142
1193
  kind: 'question',
@@ -1160,7 +1211,7 @@ export class OnDemandIntake {
1160
1211
  {
1161
1212
  id,
1162
1213
  question,
1163
- ...(scope && !target && !transition
1214
+ ...(confirmCurrent ? { options: [{ label: confirmLabel }, { label: '対象または操作を訂正する' }] } : scope && !target && !transition
1164
1215
  ? {
1165
1216
  options: [
1166
1217
  { label: '文章のみ' },
@@ -1204,24 +1255,62 @@ export class OnDemandIntake {
1204
1255
  };
1205
1256
  return pending.result;
1206
1257
  }
1207
- const answer = response.answers[0];
1258
+ let answer = response.answers[0];
1259
+ let answerQuestionId = id, answerQuestion = question;
1260
+ if (confirmCurrent && answer?.id === id && !answer.custom?.trim() &&
1261
+ answer.selected.length === 1 && answer.selected[0] === '対象または操作を訂正する') {
1262
+ answerQuestionId = `${id}-correction`;
1263
+ answerQuestion = `次の依頼について、訂正後の具体的な対象(ファイル・項目など)と希望する操作を入力してください。変更しない範囲もあれば含めてください。\n\n${state.task}`;
1264
+ await this.diagnose(state, { kind: 'question', questionRef: answerQuestionId, reason: 'clarification_required' });
1265
+ const corrected = await abortable(this.host.questions.ask({ agent: state.input.agent, signal,
1266
+ questions: [{ id: answerQuestionId, question: answerQuestion }] }), signal);
1267
+ signal.throwIfAborted();
1268
+ this.assertCurrent(state);
1269
+ if (this.#clarifications.get(sessionId) !== pending || pending.status !== 'waiting')
1270
+ return result;
1271
+ await this.host.validate(state.input);
1272
+ const afterCorrection = this.host.requestContext
1273
+ ? await this.host.requestContext(state.input, state.task)
1274
+ : (await this.host.interpret?.(state.input, state.task))?.context;
1275
+ signal.throwIfAborted();
1276
+ this.assertCurrent(state);
1277
+ if (context && (!afterCorrection || canonicalContentHash(afterCorrection) !== canonicalContentHash(context))) {
1278
+ if (afterCorrection) {
1279
+ state.requestIntentContext = afterCorrection;
1280
+ state.requestIntent = unresolvedRequestIntent(afterCorrection, 'clarification_context_changed');
1281
+ state.requestIntentContextDigest = canonicalContentHash(afterCorrection);
1282
+ }
1283
+ pending.result = { ...result, status: 'clarification_context_changed',
1284
+ detail: 'The source changed while awaiting the correction. No answer was queued; refresh the current request.' };
1285
+ return pending.result;
1286
+ }
1287
+ answer = corrected.answers[0];
1288
+ }
1208
1289
  const value = answer?.custom?.trim() || answer?.selected[0]?.trim();
1209
- if (answer?.id !== id || !value || value.length > 8192)
1290
+ if (answer?.id !== answerQuestionId || !value || value.length > 8192)
1210
1291
  return result;
1211
- // Preserve actual native sources and answers once; never recursively quote an earlier generated envelope.
1212
- // label the actual UI answer separately, and re-enter ordinary native
1213
- // capture. The reply itself is never mapped to execution authority.
1292
+ // Preserve native sources and answers once, then recapture the reply.
1293
+ // Only the exact affirmative selection below creates a scoped receipt;
1294
+ // a free-text correction is fresh user evidence, never an admission.
1214
1295
  const answerMessageId = randomUUID();
1215
1296
  const reply = clarificationReply(clarification, {
1216
1297
  id: answerMessageId,
1217
- questionId: id,
1218
- question,
1298
+ questionId: answerQuestionId,
1299
+ question: answerQuestion,
1219
1300
  value,
1220
1301
  scope: answerScope,
1221
1302
  });
1222
1303
  const content = reply.content;
1223
1304
  signal.throwIfAborted();
1224
1305
  this.assertCurrent(state);
1306
+ if (confirmCurrent && answerQuestionId === id && decision && context && !answer.custom?.trim() &&
1307
+ answer.selected.length === 1 && answer.selected[0] === confirmLabel) {
1308
+ const receipts = this.#nativeConfirmations.get(state.input.agent) ?? new Map();
1309
+ receipts.set(answerMessageId, { session: state.input.agent.session, originalTurn: state.input.turn, originalTask: state.task,
1310
+ contentDigest: canonicalContentHash(content), sourcesDigest: canonicalContentHash(clarification.sources),
1311
+ scopeDigest: this.confirmationScope(context), candidateDigest: this.confirmationCandidate(decision) });
1312
+ this.#nativeConfirmations.set(state.input.agent, receipts);
1313
+ }
1225
1314
  replies.set(answerMessageId, reply.context);
1226
1315
  this.#clarificationContexts.set(state.input.agent, replies);
1227
1316
  state.input.agent.followup({
@@ -1232,12 +1321,14 @@ export class OnDemandIntake {
1232
1321
  });
1233
1322
  await this.diagnose(state, {
1234
1323
  kind: 'answer',
1235
- questionRef: id,
1324
+ questionRef: answerQuestionId,
1236
1325
  answerRef: answerMessageId,
1237
1326
  });
1238
1327
  pending.status = 'queued';
1239
1328
  pending.result = {
1240
1329
  ...result,
1330
+ questionId: answerQuestionId,
1331
+ question: answerQuestion,
1241
1332
  status: 'clarification_answer_queued',
1242
1333
  answer: value,
1243
1334
  answerRef: answerMessageId,
@@ -1338,7 +1429,10 @@ export class OnDemandIntake {
1338
1429
  if (prior.relation !== bound.relation || prior.action !== bound.action) {
1339
1430
  throw new Error('Main-model interpretation conflicts with the configured decision provider');
1340
1431
  }
1341
- return { ...prior, evidence: bound.evidence };
1432
+ // The explicit submission is a current main-model interpretation, even
1433
+ // when its classification agrees with earlier provider extraction.
1434
+ // Preserve the explicit submission's provenance; this is not user confirmation.
1435
+ return bound;
1342
1436
  }
1343
1437
  return bound;
1344
1438
  }
@@ -1514,6 +1608,7 @@ export class OnDemandIntake {
1514
1608
  return (!this.#stopped &&
1515
1609
  !execution.signal.aborted &&
1516
1610
  UNOWNED_NATIVE_TOOLS.has(execution.name) &&
1611
+ (!state?.clarificationPending || NATIVE_EVIDENCE_READS.has(execution.name) || execution.name === 'skill') &&
1517
1612
  execution.parent === undefined &&
1518
1613
  !!state &&
1519
1614
  state.nativeUnowned === true &&
@@ -1524,9 +1619,11 @@ export class OnDemandIntake {
1524
1619
  proof.execute === this.definition(execution)?.execute &&
1525
1620
  proof.nativeUnownedDigest === canonicalContentHash(execution.arguments));
1526
1621
  }
1527
- /** Direct native Bash follows native permissions, independently of optional task intake. */
1622
+ /** Ordinary native Bash follows native policy; an issued clarification cannot be bypassed by switching tools. */
1528
1623
  nativeCommand(execution) {
1529
1624
  const state = this.bound(execution), actual = this.definition(execution), proof = this.#executions.get(execution);
1625
+ if (state?.clarificationPending)
1626
+ return false;
1530
1627
  if (!this.options.onlyWhenDeferred &&
1531
1628
  !this.#stopped &&
1532
1629
  actual &&
@@ -1557,6 +1654,15 @@ export class OnDemandIntake {
1557
1654
  proof.execute === actual.execute &&
1558
1655
  proof.nativeCommandDigest === canonicalContentHash(execution.arguments)));
1559
1656
  }
1657
+ /** Known observational/cancellation Lisp contracts stay usable while waiting.
1658
+ * Live evaluation is effect-capable; do not inspect or guess its code string. */
1659
+ pendingLispEffect(execution) {
1660
+ if (['lisp_status', 'lisp_hot_status', 'lisp_cancel'].includes(execution.name))
1661
+ return false;
1662
+ const args = execution.arguments;
1663
+ return !(execution.name === 'lisp_inspect' && execution.parent === undefined &&
1664
+ typeof args?.resultOperationId === 'string' && args.ref === undefined);
1665
+ }
1560
1666
  /** Delegate real root Lisp tools to their own effect/lease/approval authority. */
1561
1667
  nativeLisp(execution) {
1562
1668
  const state = this.bound(execution), actual = this.definition(execution), proof = this.#executions.get(execution);
@@ -1568,6 +1674,7 @@ export class OnDemandIntake {
1568
1674
  state.status !== 'answer' ||
1569
1675
  state.input.signal.aborted ||
1570
1676
  !actual ||
1677
+ (state.clarificationPending && this.pendingLispEffect(execution)) ||
1571
1678
  !isLispImplementation(actual.execute) ||
1572
1679
  (execution.agent && this.host.nativeChild?.(execution.agent)))
1573
1680
  return false;
@@ -1842,6 +1949,11 @@ export class OnDemandIntake {
1842
1949
  }
1843
1950
  try {
1844
1951
  this.assertCurrent(state);
1952
+ if (state.clarificationPending && (execution.name === 'bash' || isLispNativeCommand(execution, actual.execute) ||
1953
+ (isLispImplementation(actual.execute) && this.pendingLispEffect(execution)) ||
1954
+ (state.nativeUnowned && UNOWNED_NATIVE_TOOLS.has(execution.name) &&
1955
+ !NATIVE_EVIDENCE_READS.has(execution.name) && execution.name !== 'skill')))
1956
+ return denied('The current request is awaiting clarification. Native commands cannot bypass that question; resolve it and prepare the exact request first.');
1845
1957
  if (state.confirmationResume &&
1846
1958
  execution.name !== TASK_PREPARE_TOOL &&
1847
1959
  this.host.ready(state.input.agent, state.input.turn))