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.
- package/README.ja.md +28 -0
- package/README.ko.md +26 -0
- package/README.md +28 -0
- package/README.zh-CN.md +26 -0
- package/dist/client.cjs +97 -5
- package/dist/client.d.ts.map +1 -1
- package/dist/dsh/answer-review/contracts.js +1 -1
- package/dist/dsh/answer-review/contracts.js.map +1 -1
- package/dist/dsh/completion-report.d.ts +29 -1
- package/dist/dsh/completion-report.d.ts.map +1 -1
- package/dist/dsh/completion-report.js +58 -14
- package/dist/dsh/completion-report.js.map +1 -1
- package/dist/dsh/config.d.ts +12 -0
- package/dist/dsh/config.d.ts.map +1 -1
- package/dist/dsh/config.js +3 -0
- package/dist/dsh/config.js.map +1 -1
- package/dist/dsh/core/host.d.ts +12 -0
- package/dist/dsh/core/host.d.ts.map +1 -1
- package/dist/dsh/core/host.js +8 -4
- package/dist/dsh/core/host.js.map +1 -1
- package/dist/dsh/core/index.d.ts +32 -0
- package/dist/dsh/core/index.d.ts.map +1 -1
- package/dist/dsh/decisions/diagnostics.d.ts +4 -3
- package/dist/dsh/decisions/diagnostics.d.ts.map +1 -1
- package/dist/dsh/decisions/diagnostics.js +2 -2
- package/dist/dsh/decisions/diagnostics.js.map +1 -1
- package/dist/dsh/decisions/host.d.ts.map +1 -1
- package/dist/dsh/decisions/host.js +5 -0
- package/dist/dsh/decisions/host.js.map +1 -1
- package/dist/dsh/decisions/intent-assessment.d.ts +24 -3
- package/dist/dsh/decisions/intent-assessment.d.ts.map +1 -1
- package/dist/dsh/decisions/intent-assessment.js +36 -10
- package/dist/dsh/decisions/intent-assessment.js.map +1 -1
- package/dist/dsh/decisions/openai.d.ts.map +1 -1
- package/dist/dsh/decisions/openai.js +3 -0
- package/dist/dsh/decisions/openai.js.map +1 -1
- package/dist/dsh/decisions/service.d.ts +2 -0
- package/dist/dsh/decisions/service.d.ts.map +1 -1
- package/dist/dsh/decisions/service.js +9 -0
- package/dist/dsh/decisions/service.js.map +1 -1
- package/dist/dsh/guardrail/contracts.d.ts +1 -1
- package/dist/dsh/guardrail/contracts.d.ts.map +1 -1
- package/dist/dsh/guardrail/evidence.d.ts.map +1 -1
- package/dist/dsh/guardrail/evidence.js +4 -0
- package/dist/dsh/guardrail/evidence.js.map +1 -1
- package/dist/dsh/host-adapter.d.ts +2 -0
- package/dist/dsh/host-adapter.d.ts.map +1 -1
- package/dist/dsh/host-adapter.js +9 -5
- package/dist/dsh/host-adapter.js.map +1 -1
- package/dist/dsh/index.d.ts +10 -2
- package/dist/dsh/index.d.ts.map +1 -1
- package/dist/dsh/index.js +6 -3
- package/dist/dsh/index.js.map +1 -1
- package/dist/dsh/lisp/host-tool-dispatch.d.ts +2 -0
- package/dist/dsh/lisp/host-tool-dispatch.d.ts.map +1 -1
- package/dist/dsh/lisp/host-tool-dispatch.js +9 -1
- package/dist/dsh/lisp/host-tool-dispatch.js.map +1 -1
- package/dist/dsh/lisp/http.d.ts.map +1 -1
- package/dist/dsh/lisp/http.js +13 -1
- package/dist/dsh/lisp/http.js.map +1 -1
- package/dist/dsh/lisp/manager.d.ts +4 -0
- package/dist/dsh/lisp/manager.d.ts.map +1 -1
- package/dist/dsh/lisp/manager.js +35 -9
- package/dist/dsh/lisp/manager.js.map +1 -1
- package/dist/dsh/lisp/native-stop-receipt.d.ts +25 -0
- package/dist/dsh/lisp/native-stop-receipt.d.ts.map +1 -0
- package/dist/dsh/lisp/native-stop-receipt.js +22 -0
- package/dist/dsh/lisp/native-stop-receipt.js.map +1 -0
- package/dist/dsh/lisp/proposal-batch.d.ts.map +1 -1
- package/dist/dsh/lisp/proposal-batch.js +2 -1
- package/dist/dsh/lisp/proposal-batch.js.map +1 -1
- package/dist/dsh/lisp/sandbox.d.ts +1 -0
- package/dist/dsh/lisp/sandbox.d.ts.map +1 -1
- package/dist/dsh/lisp/sandbox.js +2 -2
- package/dist/dsh/lisp/sandbox.js.map +1 -1
- package/dist/dsh/lisp/status-text.d.ts +34 -0
- package/dist/dsh/lisp/status-text.d.ts.map +1 -0
- package/dist/dsh/lisp/status-text.js +65 -0
- package/dist/dsh/lisp/status-text.js.map +1 -0
- package/dist/dsh/lisp/surface.d.ts.map +1 -1
- package/dist/dsh/lisp/surface.js +44 -10
- package/dist/dsh/lisp/surface.js.map +1 -1
- package/dist/dsh/live-config.d.ts +49 -0
- package/dist/dsh/live-config.d.ts.map +1 -0
- package/dist/dsh/live-config.js +77 -0
- package/dist/dsh/live-config.js.map +1 -0
- package/dist/dsh/managed-file-deployment.d.ts +6 -0
- package/dist/dsh/managed-file-deployment.d.ts.map +1 -1
- package/dist/dsh/managed-file-deployment.js +17 -1
- package/dist/dsh/managed-file-deployment.js.map +1 -1
- package/dist/dsh/managed-operation-journal.d.ts +5 -1
- package/dist/dsh/managed-operation-journal.d.ts.map +1 -1
- package/dist/dsh/managed-operation-journal.js +17 -4
- package/dist/dsh/managed-operation-journal.js.map +1 -1
- package/dist/dsh/on-demand-intake.d.ts +21 -4
- package/dist/dsh/on-demand-intake.d.ts.map +1 -1
- package/dist/dsh/on-demand-intake.js +167 -55
- package/dist/dsh/on-demand-intake.js.map +1 -1
- package/dist/dsh/setup.d.ts.map +1 -1
- package/dist/dsh/setup.js +8 -2
- package/dist/dsh/setup.js.map +1 -1
- package/dist/dsh/startup-defaults-settings.d.ts +20 -0
- package/dist/dsh/startup-defaults-settings.d.ts.map +1 -0
- package/dist/dsh/startup-defaults-settings.js +27 -0
- package/dist/dsh/startup-defaults-settings.js.map +1 -0
- package/dist/dsh/startup-defaults.d.ts +41 -0
- package/dist/dsh/startup-defaults.d.ts.map +1 -0
- package/dist/dsh/startup-defaults.js +29 -0
- package/dist/dsh/startup-defaults.js.map +1 -0
- package/dist/dsh/task-startup.d.ts +7 -0
- package/dist/dsh/task-startup.d.ts.map +1 -1
- package/dist/dsh/task-startup.js +30 -9
- package/dist/dsh/task-startup.js.map +1 -1
- package/dist/enno-oduno/verifier.d.ts.map +1 -1
- package/dist/enno-oduno/verifier.js +5 -1
- package/dist/enno-oduno/verifier.js.map +1 -1
- package/dist/locale/en.json +1 -1
- package/dist/locale/ja.json +1 -1
- package/dist/locale/ko.json +1 -1
- package/dist/locale/zh-CN.json +1 -1
- package/docs/README.ja.md +1 -0
- package/docs/README.md +1 -0
- package/docs/answer-review.md +8 -0
- package/docs/lisp.md +45 -4
- package/docs/micro-judge.md +1 -1
- package/docs/on-demand-intake.md +2 -0
- package/docs/typed-decisions.md +13 -3
- package/lisp/supervisor.mjs +90 -9
- 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
|
|
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
|
|
874
|
+
if (!assessmentAllowsPreparation(result))
|
|
813
875
|
await this.deferAssessment(state, context, decision, digest);
|
|
814
876
|
}
|
|
815
|
-
return state.assessment.result
|
|
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
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
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
|
-
|
|
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 !==
|
|
1290
|
+
if (answer?.id !== answerQuestionId || !value || value.length > 8192)
|
|
1210
1291
|
return result;
|
|
1211
|
-
// Preserve
|
|
1212
|
-
//
|
|
1213
|
-
//
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
/**
|
|
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))
|