kiokuko-dsh 0.2.3 → 0.2.5

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 (141) hide show
  1. package/dist/client.cjs +223 -157
  2. package/dist/dsh/agenticreplay-event-mapper.d.ts +7 -0
  3. package/dist/dsh/agenticreplay-event-mapper.d.ts.map +1 -1
  4. package/dist/dsh/agenticreplay-event-mapper.js +52 -13
  5. package/dist/dsh/agenticreplay-event-mapper.js.map +1 -1
  6. package/dist/dsh/agenticreplay-hooks.d.ts.map +1 -1
  7. package/dist/dsh/agenticreplay-hooks.js +10 -1
  8. package/dist/dsh/agenticreplay-hooks.js.map +1 -1
  9. package/dist/dsh/agenticreplay-recorder.d.ts +5 -0
  10. package/dist/dsh/agenticreplay-recorder.d.ts.map +1 -1
  11. package/dist/dsh/agenticreplay-recorder.js +193 -20
  12. package/dist/dsh/agenticreplay-recorder.js.map +1 -1
  13. package/dist/dsh/answer-context.d.ts +8 -6
  14. package/dist/dsh/answer-context.d.ts.map +1 -1
  15. package/dist/dsh/answer-context.js +85 -21
  16. package/dist/dsh/answer-context.js.map +1 -1
  17. package/dist/dsh/coding-workflow/contracts.d.ts +5 -0
  18. package/dist/dsh/coding-workflow/contracts.d.ts.map +1 -1
  19. package/dist/dsh/coding-workflow/contracts.js +20 -3
  20. package/dist/dsh/coding-workflow/contracts.js.map +1 -1
  21. package/dist/dsh/coding-workflow/host.d.ts +4 -1
  22. package/dist/dsh/coding-workflow/host.d.ts.map +1 -1
  23. package/dist/dsh/coding-workflow/host.js +72 -44
  24. package/dist/dsh/coding-workflow/host.js.map +1 -1
  25. package/dist/dsh/coding-workflow/intent.d.ts +8 -0
  26. package/dist/dsh/coding-workflow/intent.d.ts.map +1 -1
  27. package/dist/dsh/coding-workflow/intent.js +33 -2
  28. package/dist/dsh/coding-workflow/intent.js.map +1 -1
  29. package/dist/dsh/coding-workflow/service.d.ts +214 -0
  30. package/dist/dsh/coding-workflow/service.d.ts.map +1 -1
  31. package/dist/dsh/coding-workflow/service.js +86 -6
  32. package/dist/dsh/coding-workflow/service.js.map +1 -1
  33. package/dist/dsh/coding-workflow/store.d.ts.map +1 -1
  34. package/dist/dsh/coding-workflow/store.js +10 -1
  35. package/dist/dsh/coding-workflow/store.js.map +1 -1
  36. package/dist/dsh/core/host.d.ts.map +1 -1
  37. package/dist/dsh/core/host.js +288 -48
  38. package/dist/dsh/core/host.js.map +1 -1
  39. package/dist/dsh/core/tasks.d.ts +7 -0
  40. package/dist/dsh/core/tasks.d.ts.map +1 -1
  41. package/dist/dsh/core/tasks.js +59 -8
  42. package/dist/dsh/core/tasks.js.map +1 -1
  43. package/dist/dsh/decisions/contracts.d.ts +3 -2
  44. package/dist/dsh/decisions/contracts.d.ts.map +1 -1
  45. package/dist/dsh/decisions/contracts.js +1 -1
  46. package/dist/dsh/decisions/contracts.js.map +1 -1
  47. package/dist/dsh/decisions/request-intent.d.ts +121 -0
  48. package/dist/dsh/decisions/request-intent.d.ts.map +1 -0
  49. package/dist/dsh/decisions/request-intent.js +260 -0
  50. package/dist/dsh/decisions/request-intent.js.map +1 -0
  51. package/dist/dsh/decisions/service.d.ts +5 -1
  52. package/dist/dsh/decisions/service.d.ts.map +1 -1
  53. package/dist/dsh/decisions/service.js +2 -1
  54. package/dist/dsh/decisions/service.js.map +1 -1
  55. package/dist/dsh/execution-support.d.ts +2 -0
  56. package/dist/dsh/execution-support.d.ts.map +1 -1
  57. package/dist/dsh/execution-support.js +36 -8
  58. package/dist/dsh/execution-support.js.map +1 -1
  59. package/dist/dsh/host-adapter/admission.d.ts +4 -1
  60. package/dist/dsh/host-adapter/admission.d.ts.map +1 -1
  61. package/dist/dsh/host-adapter/admission.js +294 -12
  62. package/dist/dsh/host-adapter/admission.js.map +1 -1
  63. package/dist/dsh/host-adapter/routing.d.ts +2 -1
  64. package/dist/dsh/host-adapter/routing.d.ts.map +1 -1
  65. package/dist/dsh/host-adapter/routing.js +32 -18
  66. package/dist/dsh/host-adapter/routing.js.map +1 -1
  67. package/dist/dsh/host-adapter/turn-state.d.ts +6 -0
  68. package/dist/dsh/host-adapter/turn-state.d.ts.map +1 -1
  69. package/dist/dsh/host-adapter/turn-state.js +19 -1
  70. package/dist/dsh/host-adapter/turn-state.js.map +1 -1
  71. package/dist/dsh/host-adapter.d.ts.map +1 -1
  72. package/dist/dsh/host-adapter.js +71 -8
  73. package/dist/dsh/host-adapter.js.map +1 -1
  74. package/dist/dsh/intake-clarification.d.ts +2 -2
  75. package/dist/dsh/intake-clarification.d.ts.map +1 -1
  76. package/dist/dsh/intake-clarification.js +13 -4
  77. package/dist/dsh/intake-clarification.js.map +1 -1
  78. package/dist/dsh/intake-gate.d.ts +5 -0
  79. package/dist/dsh/intake-gate.d.ts.map +1 -1
  80. package/dist/dsh/intake-gate.js +19 -5
  81. package/dist/dsh/intake-gate.js.map +1 -1
  82. package/dist/dsh/lisp/candidate.d.ts +1 -1
  83. package/dist/dsh/lisp/surface.d.ts +1 -1
  84. package/dist/dsh/memory-owner.d.ts +24 -0
  85. package/dist/dsh/memory-owner.d.ts.map +1 -0
  86. package/dist/dsh/memory-owner.js +35 -0
  87. package/dist/dsh/memory-owner.js.map +1 -0
  88. package/dist/dsh/memory-request-fence.d.ts +37 -0
  89. package/dist/dsh/memory-request-fence.d.ts.map +1 -0
  90. package/dist/dsh/memory-request-fence.js +48 -0
  91. package/dist/dsh/memory-request-fence.js.map +1 -0
  92. package/dist/dsh/message-sources.d.ts.map +1 -1
  93. package/dist/dsh/message-sources.js +2 -1
  94. package/dist/dsh/message-sources.js.map +1 -1
  95. package/dist/dsh/models/profiles.d.ts +25 -0
  96. package/dist/dsh/models/profiles.d.ts.map +1 -1
  97. package/dist/dsh/models/profiles.js +39 -6
  98. package/dist/dsh/models/profiles.js.map +1 -1
  99. package/dist/dsh/models/service.d.ts +33 -1
  100. package/dist/dsh/models/service.d.ts.map +1 -1
  101. package/dist/dsh/models/service.js +130 -48
  102. package/dist/dsh/models/service.js.map +1 -1
  103. package/dist/dsh/on-demand-intake.d.ts +71 -5
  104. package/dist/dsh/on-demand-intake.d.ts.map +1 -1
  105. package/dist/dsh/on-demand-intake.js +299 -70
  106. package/dist/dsh/on-demand-intake.js.map +1 -1
  107. package/dist/dsh/owned-execution.d.ts +3 -0
  108. package/dist/dsh/owned-execution.d.ts.map +1 -1
  109. package/dist/dsh/owned-execution.js +7 -0
  110. package/dist/dsh/owned-execution.js.map +1 -1
  111. package/dist/dsh/request-memory.d.ts +2 -0
  112. package/dist/dsh/request-memory.d.ts.map +1 -1
  113. package/dist/dsh/request-memory.js +33 -2
  114. package/dist/dsh/request-memory.js.map +1 -1
  115. package/dist/dsh/setup.d.ts +2 -2
  116. package/dist/dsh/setup.d.ts.map +1 -1
  117. package/dist/dsh/setup.js +37 -14
  118. package/dist/dsh/setup.js.map +1 -1
  119. package/dist/dsh/skill-prompts.json +1 -1
  120. package/dist/dsh/standard-skills.d.ts +2 -1
  121. package/dist/dsh/standard-skills.d.ts.map +1 -1
  122. package/dist/dsh/standard-skills.js +2 -1
  123. package/dist/dsh/standard-skills.js.map +1 -1
  124. package/dist/dsh/task-intake.d.ts +6 -0
  125. package/dist/dsh/task-intake.d.ts.map +1 -1
  126. package/dist/dsh/task-intake.js +7 -2
  127. package/dist/dsh/task-intake.js.map +1 -1
  128. package/dist/locale/en.json +1 -1
  129. package/dist/locale/ja.json +1 -1
  130. package/dist/locale/ko.json +1 -1
  131. package/dist/locale/zh-CN.json +1 -1
  132. package/dist/models-client.cjs +223 -157
  133. package/dist/models-client.d.ts.map +1 -1
  134. package/dist/models-client.js +261 -166
  135. package/dist/models-client.js.map +1 -1
  136. package/docs/coding-workflow.md +3 -1
  137. package/docs/dsh-plugin.md +28 -5
  138. package/docs/kiokuko-models.md +25 -13
  139. package/docs/on-demand-intake.md +13 -7
  140. package/package.json +3 -2
  141. package/skills/japanese-translation-for-oss-models/SKILL.md +5 -3
@@ -3,6 +3,7 @@ import { bindMemoryApplication, memoryRetrievalStatus } from '../../memory/appli
3
3
  import { createMemoryReuseRuntime } from '../memory-reuse.js';
4
4
  import { dshTurnRequestId } from '../intake-profile-resolver.js';
5
5
  import { classifyTaskForIntake, selectInstalledSkills } from '../decisions/workflows.js';
6
+ import { classifyRequestIntent, createRequestIntentContext, rebindRequestIntentDecision, requestIntentMessageRefs, requestIntentReferenceIds, requestIntentTaskType } from '../decisions/request-intent.js';
6
7
  import { ANSWER_REVIEW_FORM, hasHumanInput } from '../answer-review/contracts.js';
7
8
  import { refreshContinuedTaskContext } from '../task-intake.js';
8
9
  import { saveSessionNotice } from '../plugin-records.js';
@@ -28,8 +29,12 @@ import { ENNO_APPLICABLE_TASK_TYPES } from '../../enno-oduno/types.js';
28
29
  import { supersedeBoundaryJobsAtOrBeforeRevisionInTransaction, supersedeOutboxAtOrBeforeRevisionInTransaction } from '../turn-process.js';
29
30
  import { unexecutedRunInput } from '../input-claim.js';
30
31
  import { resetLoopGuardForUserInTransaction } from '../loop-guard.js';
31
- import { codingContinuation } from '../coding-workflow/intent.js';
32
32
  import { readCodingWorkflow } from '../coding-workflow/store.js';
33
+ import { markCodingScopeChange } from '../coding-workflow/service.js';
34
+ import { readExecutionFrame } from '../execution-frame.js';
35
+ import { assertMemoryOwner, createMemoryOwner, memoryOwnerKind } from '../memory-owner.js';
36
+ import { findSecret } from '../../memory/secrets.js';
37
+ import { executionClarification, isContinuationReference } from '../intake-clarification.js';
33
38
  export function createAdmission({ native, skills, tools, userQuestions, sessions, agents, deepPlanning, modelCatalog, modelCompatibility, modelRoutes, now, runtime, decisions, answerReview, delegation, executionSupport, ennoMemory, memoryFinalizer, autoReview, sessionMirror, akinatorMemoryConfig, memoryRetrievalConfig, completionMode, turnState, getSelection, setSelection, refreshEnnoMemory, executionBinding, captureInitialInput, contextMessages, resolveIdleClose, retireSupersededRun, cancelBoundarySession, isSelectionBlocked, closeTurn, readStateForRun, capabilityCatalog, sessionEventSource, objectRecord, isHumanMessage, textFromMessages, pluginContinuationId, isLoopRecoveryMessage, }) {
34
39
  const currentSession = turnState.currentSession;
35
40
  const currentForAgentEvent = turnState.currentForAgentEvent;
@@ -45,6 +50,16 @@ export function createAdmission({ native, skills, tools, userQuestions, sessions
45
50
  }).profileHints.taskType;
46
51
  return incomingType !== null && !ENNO_APPLICABLE_TASK_TYPES.includes(incomingType);
47
52
  }
53
+ function eventTurn(event) {
54
+ const data = typeof event.data === 'object' && event.data !== null && !Array.isArray(event.data)
55
+ ? event.data : undefined;
56
+ return Number.isSafeInteger(data?.turn) ? data.turn : undefined;
57
+ }
58
+ function continuesCodingScope(event) {
59
+ const intent = event.requestIntent;
60
+ return !!intent && (intent.relation === 'same_scope_continuation' || intent.relation === 'scope_change')
61
+ && intent.action !== 'answer' && intent.action !== 'unknown';
62
+ }
48
63
  let prepareGeneration = 0;
49
64
  const continuedTurns = new Map();
50
65
  const resumedTurns = new Map();
@@ -83,7 +98,11 @@ export function createAdmission({ native, skills, tools, userQuestions, sessions
83
98
  },
84
99
  });
85
100
  }
86
- if (result.admitted && result.prepared.run.status === 'active' && currentSession(event.sessionId)?.prepareGeneration === generation && !delegation.isChild(event.nativeAgent ?? {})) {
101
+ if (result.admitted && event.requestIntent?.relation !== 'scope_change' && result.prepared.run.status === 'active'
102
+ && result.prepared.memoryOwner?.kind === 'execution'
103
+ && currentSession(event.sessionId)?.prepareGeneration === generation && !delegation.isChild(event.nativeAgent ?? {})) {
104
+ assertMemoryOwner(result.prepared.memoryOwner, { agentId: event.agent.id, sessionId: event.sessionId, turn: event.turn,
105
+ requestDigest: canonicalContentHash(event.task), runId: result.prepared.run.runId }, 'execution');
87
106
  await runtime.withDatabase(db => {
88
107
  if (currentSession(event.sessionId)?.prepareGeneration !== generation)
89
108
  return;
@@ -100,6 +119,18 @@ export function createAdmission({ native, skills, tools, userQuestions, sessions
100
119
  catch { /* Unsupported or unavailable review source must not veto a native request. */ }
101
120
  }
102
121
  };
122
+ async function hasPendingExecutionDiscussion(sessionId, cwd) {
123
+ return runtime.withDatabase(async (db) => {
124
+ const project = await resolveProjectWorkspaceReadOnly(db, cwd, { allowDirectory: true });
125
+ if (!project)
126
+ return false;
127
+ const activeRuns = db.prepare(`SELECT run_id FROM ledger_runs
128
+ WHERE workspace=? AND dsh_session_id=? AND status='active'`).all(project.workspace, sessionId);
129
+ if (activeRuns.length !== 1)
130
+ return false;
131
+ return readExecutionSelection(db, activeRuns[0].run_id)?.value.discussion !== undefined;
132
+ });
133
+ }
103
134
  const refreshContinuedWorkLease = async (item, expected) => {
104
135
  const expectedWorkUnitId = expected.nextAction === 'execute_work_unit'
105
136
  ? expected.directive?.workUnit?.id
@@ -200,7 +231,8 @@ export function createAdmission({ native, skills, tools, userQuestions, sessions
200
231
  // context deliveries that finish out of order.
201
232
  const generation = ++prepareGeneration;
202
233
  const prior = currentForAgentEvent(event.agent.id, event.sessionId, undefined, event.nativeSession, event.nativeAgent);
203
- if (prior && prior.turn === event.turn && !prior.closed && prior.task !== event.task && hasHumanInput(event.nativeMessages ?? [])) {
234
+ if (prior && prior.turn === event.turn && !prior.closed && prior.task !== event.task && hasHumanInput(event.nativeMessages ?? [])
235
+ && event.requestIntent?.relation === 'new') {
204
236
  const choice = getSelection(prior.runId);
205
237
  if (choice?.value.status === 'ready')
206
238
  this.supersededChoices.set(`${event.sessionId}\u0000${event.turn}`, choice);
@@ -223,11 +255,33 @@ export function createAdmission({ native, skills, tools, userQuestions, sessions
223
255
  profileHints: event.profileHints ?? null,
224
256
  ...(event.deferTaskTypeInference ? { deferTaskTypeInference: true } : {}),
225
257
  evidence: event.evidence ?? null,
258
+ requestIntent: event.requestIntent ?? null,
259
+ requestIntentContextDigest: event.requestIntentContext ? canonicalContentHash(event.requestIntentContext) : null,
260
+ memoryOwner: event.memoryOwner ?? null,
226
261
  skillDiscoveryMode: event.skillDiscoveryMode ?? null,
227
262
  catalogDigest: event.capabilities.digest,
228
263
  });
264
+ const unresolvedBinding = canonicalContentHash({ sessionId: event.sessionId, agentId: event.agent.id,
265
+ turn: event.turn, sourceStartSeq: event.sourceStartSeq ?? null, task: event.task, cwd: event.cwd,
266
+ evidence: event.evidence ?? null, context: event.requestIntentContext ?? null, catalogDigest: event.capabilities.digest,
267
+ skillDiscoveryMode: event.skillDiscoveryMode ?? null });
229
268
  const cached = continuedTurns.get(cacheKey) ?? resumedTurns.get(cacheKey);
230
269
  if (cached !== undefined) {
270
+ if (cached.fingerprint !== fingerprint && 'unresolvedBinding' in cached && cached.unresolvedBinding === unresolvedBinding
271
+ && cached.nativeAgent === event.nativeAgent && cached.nativeSession === event.nativeSession
272
+ && event.requestIntentContext && event.requestIntent
273
+ && continuesCodingScope(event) && rebindRequestIntentDecision(event.requestIntent, event.requestIntentContext)) {
274
+ // A host-owned confirmation may resume before optional interpretation.
275
+ // Resolve that exact unresolved input once; changing resolved input still conflicts.
276
+ const kind = memoryOwnerKind(event.requestIntent, event.profileHints?.taskType);
277
+ const prepared = { ...cached.result.prepared,
278
+ memoryOwner: createMemoryOwner(kind, { agentId: event.agent.id, sessionId: event.sessionId, turn: event.turn,
279
+ requestDigest: canonicalContentHash(event.task), runId: cached.result.prepared.run.runId }) };
280
+ const resolved = await this.choose(event, { ...cached.result, prepared });
281
+ continuedTurns.set(cacheKey, { fingerprint, result: resolved, ...(event.nativeAgent ? { nativeAgent: event.nativeAgent } : {}), ...(event.nativeSession ? { nativeSession: event.nativeSession } : {}) });
282
+ await bindAndRecord(event, resolved, generation);
283
+ return resolved;
284
+ }
231
285
  if (cached.fingerprint !== fingerprint
232
286
  || cached.nativeAgent !== event.nativeAgent
233
287
  || cached.nativeSession !== event.nativeSession) {
@@ -247,12 +301,36 @@ export function createAdmission({ native, skills, tools, userQuestions, sessions
247
301
  if (!hasHumanInput(reviewMessages) && reviewMessages.some(message => objectRecord(objectRecord(message)?.source)?.form === ANSWER_REVIEW_FORM)) {
248
302
  if (!previous || !event.nativeAgent || !await answerReview.accept(event.nativeAgent, reviewMessages, event.turn, event.capabilities.digest))
249
303
  throw new Error('Unbound answer review continuation');
250
- const continued = { admitted: true, prepared: previous.prepared, catalog: previous.catalog };
304
+ // Only the coordinator's exact reserved message may carry this owner
305
+ // into another native turn; the request and run remain unchanged.
306
+ const owner = previous.prepared.memoryOwner;
307
+ const prepared = owner ? { ...previous.prepared,
308
+ memoryOwner: createMemoryOwner(owner.kind, { ...owner, turn: event.turn }) } : previous.prepared;
309
+ const continued = { admitted: true, prepared, catalog: previous.catalog };
251
310
  continuedTurns.set(cacheKey, { fingerprint, result: continued, nativeAgent: event.nativeAgent, ...(event.nativeSession === undefined ? {} : { nativeSession: event.nativeSession }) });
252
311
  await bindAndRecord(event, continued, generation);
253
312
  return continued;
254
313
  }
255
314
  if (previous !== undefined && previous.turn === event.turn && !previous.closed) {
315
+ if (previous.task !== event.task && hasHumanInput(event.nativeMessages ?? [])) {
316
+ if (!continuesCodingScope(event))
317
+ return { admitted: false, prepared: previous.prepared, catalog: event.capabilities };
318
+ const workflow = await runtime.withDatabase(db => readCodingWorkflow(db, previous.runId));
319
+ const scopeChanging = !!workflow && event.requestIntent?.relation === 'scope_change';
320
+ if (scopeChanging && event.requestIntent) {
321
+ const evidenceRefs = event.requestIntentContext ? requestIntentReferenceIds(event.requestIntentContext) : [];
322
+ await runtime.withDatabase(db => markCodingScopeChange(db, { runId: previous.runId, sessionId: previous.sessionId, repositoryRoot: previous.repositoryRoot }, `request-intent:${event.sessionId}:${event.turn}:${event.requestIntent.evidence.requestDigest}`, event.requestIntent.evidence.requestDigest, evidenceRefs));
323
+ }
324
+ const ownerKind = event.memoryOwner ?? memoryOwnerKind(event.requestIntent, event.profileHints?.taskType ?? previous.prepared.intake.profile.taskType);
325
+ const prepared = { ...previous.prepared,
326
+ ...(scopeChanging ? { context: null, memoryPolicy: { memoryReasoningRequired: false, contextWithheld: false, withheldReason: null, deliveryEmpty: true } } : {}),
327
+ memoryOwner: createMemoryOwner(ownerKind, { agentId: event.agent.id, sessionId: event.sessionId, turn: event.turn,
328
+ requestDigest: canonicalContentHash(event.task), runId: previous.runId }) };
329
+ const selected = await this.choose(event, { admitted: !event.signal.aborted, prepared, catalog: event.capabilities });
330
+ if (selected.admitted)
331
+ await bindAndRecord(event, selected, generation);
332
+ return selected;
333
+ }
256
334
  // Model routing prepares before native pre-step. Reuse that exact
257
335
  // intake result instead of treating its newly active run as a cold
258
336
  // resume (which deliberately starts without historical context).
@@ -304,7 +382,7 @@ export function createAdmission({ native, skills, tools, userQuestions, sessions
304
382
  // Child results and other generated turns continue the admitted Goal.
305
383
  // Only a new human request can replace it without an explicit continuation.
306
384
  const resumeCoding = coding && !['completed', 'cancelled', 'superseded'].includes(coding.state)
307
- && (!hasHumanInput(event.nativeMessages ?? []) || codingContinuation(event.task));
385
+ && (!hasHumanInput(event.nativeMessages ?? []) || continuesCodingScope(event));
308
386
  const continuePrevious = resumeCoding || selection && (selection.status !== 'ready' || selection.mode === 'normal' && previous.failed)
309
387
  ? true : previousWasChat
310
388
  ? event.profileHints?.taskType === 'chat'
@@ -315,6 +393,12 @@ export function createAdmission({ native, skills, tools, userQuestions, sessions
315
393
  && (previousState.nextAction !== 'complete' || executionSupport.paused(previous.sessionId));
316
394
  if (continuePrevious) {
317
395
  this.assertCatalog(previous.catalog, event.capabilities);
396
+ const intent = event.requestIntent;
397
+ const scopeChanging = !!coding && intent?.relation === 'scope_change' && intent.action !== 'answer' && intent.action !== 'unknown';
398
+ if (scopeChanging && intent) {
399
+ const evidenceRefs = event.requestIntentContext ? requestIntentReferenceIds(event.requestIntentContext) : [];
400
+ await runtime.withDatabase(db => markCodingScopeChange(db, { runId: previous.runId, sessionId: previous.sessionId, repositoryRoot: previous.repositoryRoot }, `request-intent:${event.sessionId}:${event.turn}:${intent.evidence.requestDigest}`, intent.evidence.requestDigest, evidenceRefs));
401
+ }
318
402
  const refreshedWork = await refreshContinuedWorkLease(previous, previousState);
319
403
  if (refreshedWork !== undefined) {
320
404
  previous.prepared = { ...previous.prepared, ennoOduno: refreshedWork.state };
@@ -322,7 +406,10 @@ export function createAdmission({ native, skills, tools, userQuestions, sessions
322
406
  turnState.applyPolicy(previous.runId, next);
323
407
  }
324
408
  let continuedMemory;
325
- if (!ennoMemory.ownsActiveRefresh(previous.prepared.ennoOduno)) {
409
+ if (scopeChanging)
410
+ continuedMemory = { context: null,
411
+ memoryPolicy: { memoryReasoningRequired: false, contextWithheld: false, withheldReason: null, deliveryEmpty: true } };
412
+ else if (!ennoMemory.ownsActiveRefresh(previous.prepared.ennoOduno)) {
326
413
  const captured = previous.prepared, started = performance.now();
327
414
  const inboxIdentity = () => canonicalContentHash([...(event.nativeAgent?.inbox?.nextStep ?? []), ...(event.nativeAgent?.inbox?.nextTurn ?? [])].filter(isHumanMessage));
328
415
  const pendingInput = inboxIdentity();
@@ -349,10 +436,15 @@ export function createAdmission({ native, skills, tools, userQuestions, sessions
349
436
  /* Optional retrieval failure retains the previous delivery, whose state is validated before injection. */
350
437
  }
351
438
  }
352
- const continued = await this.choose(event, { admitted: !event.signal.aborted, prepared: continuedMemory ? { ...previous.prepared, ...continuedMemory } : previous.prepared, catalog: event.capabilities });
439
+ const ownerKind = event.memoryOwner ?? memoryOwnerKind(intent, event.profileHints?.taskType ?? previous.prepared.intake.profile.taskType);
440
+ const continuedPrepared = { ...previous.prepared, ...(continuedMemory ?? {}),
441
+ memoryOwner: createMemoryOwner(ownerKind, { agentId: event.agent.id, sessionId: event.sessionId, turn: event.turn,
442
+ requestDigest: canonicalContentHash(event.task), runId: previous.runId }) };
443
+ const continued = await this.choose(event, { admitted: !event.signal.aborted, prepared: continuedPrepared, catalog: event.capabilities });
353
444
  if (continued.admitted) {
354
445
  continuedTurns.set(cacheKey, {
355
446
  fingerprint,
447
+ ...(event.requestIntent?.relation === 'unknown' || event.requestIntent?.action === 'unknown' ? { unresolvedBinding } : {}),
356
448
  result: continued,
357
449
  ...(event.profileHints === undefined ? {} : { profileHints: event.profileHints }),
358
450
  ...(event.nativeAgent === undefined ? {} : { nativeAgent: event.nativeAgent }),
@@ -538,6 +630,49 @@ export function createAdmission({ native, skills, tools, userQuestions, sessions
538
630
  }
539
631
  }
540
632
  const gate = new CapturingGate(runtime, userQuestions === undefined ? undefined : createDshIntakeAnswerer(userQuestions), (context) => capabilityCatalog(skills, tools, context), true, akinatorMemoryConfig, decisions, completionMode, memoryRetrievalConfig);
633
+ const refreshMemoryOwner = async (item, signal) => {
634
+ const prepared = item.prepared;
635
+ const owner = prepared.memoryOwner;
636
+ if (!owner || owner.kind !== 'execution')
637
+ throw new KiokukoError('CONFLICT', 'memory_owner_unavailable');
638
+ const ownerIdentity = { agentId: item.agentId, sessionId: item.sessionId, turn: item.turn,
639
+ requestDigest: canonicalContentHash(item.task), runId: item.runId };
640
+ assertMemoryOwner(owner, ownerIdentity, 'execution');
641
+ const started = performance.now();
642
+ const refreshed = await runtime.withDatabase(async (database, embedding) => {
643
+ const assertCurrent = () => {
644
+ signal.throwIfAborted();
645
+ if (currentSession(item.sessionId) !== item || item.prepared !== prepared || item.closed || item.failed
646
+ || item.nativeAgent === undefined || agents?.get(item.nativeAgent.id) !== item.nativeAgent
647
+ || item.nativeSession === undefined || sessions?.get(item.sessionId) !== item.nativeSession) {
648
+ throw new KiokukoError('CONFLICT', 'memory_owner_changed');
649
+ }
650
+ assertMemoryOwner(owner, ownerIdentity, 'execution');
651
+ if (performance.now() - started > 1000 + (decisions.memoryReuse.mode === 'auto' ? decisions.memoryReuse.budgetMs : 0)) {
652
+ throw new KiokukoError('CONFLICT', 'memory_owner_rebuild_deadline');
653
+ }
654
+ };
655
+ if (embedding.mode === 'required')
656
+ throw new KiokukoError('SERVICE_UNAVAILABLE', 'required_embedding_unavailable');
657
+ const capabilities = [...item.catalog.skills, ...item.catalog.tools];
658
+ const memoryReuse = await createMemoryReuseRuntime(decisions, `run:${item.runId}`, signal);
659
+ return refreshContinuedTaskContext({ database, memoryReuse, memoryRetrieval: memoryRetrievalConfig, prepared,
660
+ task: item.memoryInput ?? item.task, capabilities, assertCurrent,
661
+ validateCapabilities: async () => {
662
+ const fresh = await capabilityCatalog(skills, tools, { agent: { id: item.agentId },
663
+ ...(item.nativeAgent ? { nativeAgent: item.nativeAgent } : {}), cwd: item.cwd, signal });
664
+ gate.assertCatalog(item.catalog, fresh);
665
+ assertCurrent();
666
+ } });
667
+ });
668
+ signal.throwIfAborted();
669
+ if (currentSession(item.sessionId) !== item || item.prepared !== prepared)
670
+ throw new KiokukoError('CONFLICT', 'memory_owner_changed');
671
+ item.prepared = { ...prepared, ...refreshed };
672
+ await runtime.withDatabase(database => bindMemoryApplication(database, {
673
+ runId: item.runId, workspace: item.workspace, sessionId: item.sessionId, repositoryRoot: item.repositoryRoot,
674
+ }, item.prepared.intake.profile, item.prepared.context, memoryRetrievalStatus(database, item.workspace, item.prepared.context, item.prepared.memoryPolicy.contextWithheld)));
675
+ };
541
676
  resumeExistingRun = async (event) => runtime.withDatabase(async (database) => {
542
677
  const project = await resolveProjectWorkspaceReadOnly(database, event.cwd, { allowDirectory: true });
543
678
  if (project === undefined)
@@ -554,7 +689,7 @@ export function createAdmission({ native, skills, tools, userQuestions, sessions
554
689
  OR (ec.run_id IS NULL AND (json_extract(es.state_json, '$.paused') = 1 OR xs.run_id IS NOT NULL)))
555
690
  AND (cw.run_id IS NULL OR (? = 1 AND json_extract(cw.snapshot_json, '$.state') NOT IN ('completed', 'cancelled', 'superseded')))
556
691
  ORDER BY lr.created_at, lr.run_id LIMIT 2
557
- `).all(project.workspace, event.sessionId, project.repositoryRoot, codingContinuation(event.task) ? 1 : 0);
692
+ `).all(project.workspace, event.sessionId, project.repositoryRoot, continuesCodingScope(event) ? 1 : 0);
558
693
  if (candidates.length === 0)
559
694
  return undefined;
560
695
  if (candidates.length !== 1)
@@ -601,6 +736,10 @@ export function createAdmission({ native, skills, tools, userQuestions, sessions
601
736
  });
602
737
  const canonicalCwd = realpathSync(event.cwd);
603
738
  const prepared = {
739
+ memoryOwner: createMemoryOwner(event.memoryOwner ?? memoryOwnerKind(event.requestIntent, intake.profile.taskType), {
740
+ agentId: event.agent.id, sessionId: event.sessionId, turn: event.turn,
741
+ requestDigest: canonicalContentHash(event.task), runId,
742
+ }),
604
743
  project,
605
744
  executionContext: {
606
745
  canonicalCwd,
@@ -631,7 +770,97 @@ export function createAdmission({ native, skills, tools, userQuestions, sessions
631
770
  turnState.stageLease(runId, decision.executionLease);
632
771
  return { admitted: !event.signal.aborted, prepared, catalog: event.capabilities };
633
772
  });
634
- const mapPreStep = async (payload, advisoryType) => {
773
+ const requestIntentContextFor = async (input, sessionId, nativeSession, previous, task) => {
774
+ const human = input.messages.filter(isHumanMessage);
775
+ if (!human.length || !task.trim())
776
+ return undefined;
777
+ const sessionHeader = objectRecord(objectRecord(nativeSession)?.header);
778
+ const cwd = typeof sessionHeader?.cwd === 'string' ? sessionHeader.cwd : undefined;
779
+ if (!cwd)
780
+ return undefined;
781
+ const messageRefs = requestIntentMessageRefs(human, task);
782
+ let activeGoal;
783
+ let proposals = [];
784
+ const sameOwner = previous && previous.sessionId === sessionId && previous.nativeAgent === input.agent && previous.nativeSession === nativeSession;
785
+ const persistedRun = sameOwner ? undefined : await runtime.withDatabase(async (database) => {
786
+ const project = await resolveProjectWorkspaceReadOnly(database, cwd, { allowDirectory: true });
787
+ if (!project)
788
+ return undefined;
789
+ const candidates = database.prepare(`SELECT lr.run_id AS runId,
790
+ ec.orchestration_session_id AS orchestrationId
791
+ FROM ledger_runs AS lr
792
+ LEFT JOIN dsh_coding_workflows AS cw ON cw.run_id = lr.run_id
793
+ LEFT JOIN enno_contracts AS ec ON ec.run_id = lr.run_id
794
+ WHERE lr.workspace = ? AND lr.dsh_session_id = ? AND lr.status = 'active'
795
+ AND ((json_extract(cw.snapshot_json, '$.repositoryRoot') = ?
796
+ AND json_extract(cw.snapshot_json, '$.sessionId') = ?
797
+ AND json_extract(cw.snapshot_json, '$.state') NOT IN ('completed', 'cancelled', 'superseded'))
798
+ OR (ec.repository_root = ? AND ec.status NOT IN ('completed', 'cancelled', 'blocked')))
799
+ ORDER BY lr.created_at, lr.run_id LIMIT 2`)
800
+ .all(project.workspace, sessionId, project.repositoryRoot, sessionId, project.repositoryRoot);
801
+ if (candidates.length > 1)
802
+ throw new KiokukoError('CONFLICT', 'Multiple active tasks match this session; refusing to guess');
803
+ return candidates[0] === undefined ? undefined : { ...candidates[0], workspace: project.workspace };
804
+ });
805
+ const runId = sameOwner ? previous.runId : persistedRun?.runId;
806
+ if (runId) {
807
+ const state = await runtime.withDatabase(database => {
808
+ const workflow = readCodingWorkflow(database, runId);
809
+ const definition = workflow?.definition;
810
+ const frame = readExecutionFrame(database, runId);
811
+ const enno = !workflow && persistedRun?.runId === runId && persistedRun.orchestrationId !== null
812
+ ? readEnnoSnapshot(database, { runId, workspace: persistedRun.workspace, orchestrationId: persistedRun.orchestrationId })
813
+ : undefined;
814
+ const active = definition ? {
815
+ runId, workflowRevision: workflow.revision, goalId: definition.goal.goalId,
816
+ goalRevision: definition.goal.revision, scopeRevision: workflow.scopeVersion, ideal: definition.goal.ideal,
817
+ target: definition.goal.target, outOfScope: [...definition.goal.outOfScope], constraints: [...definition.goal.constraints],
818
+ } : workflow ? { runId, workflowRevision: workflow.revision, scopeRevision: workflow.scopeVersion, outOfScope: [], constraints: [] }
819
+ : enno ? {
820
+ runId, workflowRevision: enno.revision, goalRevision: enno.revision, scopeRevision: enno.revision,
821
+ ideal: enno.ideal?.objective ?? enno.handoff.objective,
822
+ ...(enno.handoff.target === null ? {} : { target: enno.handoff.target }),
823
+ outOfScope: [...enno.contract.exclusions], constraints: [...enno.handoff.constraints],
824
+ }
825
+ : sameOwner && previous.prepared.ennoOduno.applicable ? {
826
+ runId,
827
+ ...(previous.prepared.ennoOduno.contractRevision === null ? {} : {
828
+ workflowRevision: previous.prepared.ennoOduno.contractRevision,
829
+ goalRevision: previous.prepared.ennoOduno.contractRevision,
830
+ scopeRevision: previous.prepared.ennoOduno.contractRevision,
831
+ }),
832
+ ...(previous.prepared.ennoOduno.directive?.objective ? { ideal: previous.prepared.ennoOduno.directive.objective } : {}),
833
+ ...(previous.prepared.intake.profile.target ? { target: previous.prepared.intake.profile.target } : {}),
834
+ outOfScope: [],
835
+ constraints: previous.prepared.intake.profile.constraints ? [previous.prepared.intake.profile.constraints] : [],
836
+ } : undefined;
837
+ const candidates = (frame?.conditions ?? []).filter(condition => condition.approval === 'proposed').slice(0, 7)
838
+ .map(condition => ({ id: `condition-${canonicalContentHash([runId, frame.revision, condition.field, condition.text, condition.planRevision])}`,
839
+ revision: condition.planRevision ?? frame.revision, digest: canonicalContentHash(condition),
840
+ text: `${condition.field}: ${condition.text}`, field: condition.field }));
841
+ return { active, candidates };
842
+ });
843
+ activeGoal = state.active;
844
+ proposals = state.candidates;
845
+ }
846
+ const eventSource = sessionEventSource(nativeSession);
847
+ const precedingAssistant = [...eventSource.snapshotEvents()].reverse().find(event => event.type === 'assistant/message'
848
+ && (eventTurn(event) ?? Number.MAX_SAFE_INTEGER) < input.turn);
849
+ if (precedingAssistant && proposals.length < 8) {
850
+ const data = objectRecord(precedingAssistant.data);
851
+ const message = objectRecord(data?.message) ?? data;
852
+ const content = Array.isArray(message?.content) ? message.content : [];
853
+ const text = content.filter((block) => objectRecord(block)?.type === 'text')
854
+ .map((block) => objectRecord(block)?.text).filter((value) => typeof value === 'string').join('\n').trim();
855
+ const messageId = typeof message?.id === 'string' ? message.id : `seq:${precedingAssistant.seq}`;
856
+ if (message?.role === 'assistant' && text && text.length <= 8192 && findSecret(text) === undefined) {
857
+ proposals = [...proposals, { id: `assistant-${canonicalContentHash([sessionId, messageId])}`, revision: null,
858
+ digest: canonicalContentHash(text), text }];
859
+ }
860
+ }
861
+ return createRequestIntentContext({ task, messageRefs, ...(activeGoal ? { activeGoal } : {}), proposals: proposals.slice(0, 8) });
862
+ };
863
+ const mapPreStep = async (payload, advisoryType, suppliedIntent, interpretationOnly = false) => {
635
864
  const nativeSession = payload.agent.session;
636
865
  const registered = nativeSession === undefined && payload.agent.sessionId === undefined
637
866
  ? sessions?.get(payload.agent.id)
@@ -668,7 +897,8 @@ export function createAdmission({ native, skills, tools, userQuestions, sessions
668
897
  // model, while any supplied step-local user text still replaces this
669
898
  // fallback and is recorded as the continuation instruction.
670
899
  const reviewing = !hasHumanInput(payload.messages) && payload.messages.some(message => objectRecord(objectRecord(message)?.source)?.form === ANSWER_REVIEW_FORM);
671
- const task = bound?.task ?? (reviewing && previous ? previous.task : textFromMessages(payload.messages, previous?.task));
900
+ const intentTask = hasHumanInput(payload.messages) ? textFromMessages(payload.messages.filter(isHumanMessage), '') : '';
901
+ const task = intentTask.trim() ? intentTask : bound?.task ?? (reviewing && previous ? previous.task : textFromMessages(payload.messages, previous?.task));
672
902
  let profile = (() => {
673
903
  if (bound !== undefined)
674
904
  return bound.profileHints;
@@ -683,20 +913,62 @@ export function createAdmission({ native, skills, tools, userQuestions, sessions
683
913
  return inferred === null || previousType === 'chat' && inferred === 'chat' ? { taskType: previousType } : undefined;
684
914
  })();
685
915
  let deferTaskTypeInference = bound?.deferTaskTypeInference ?? (reviewing ? previous?.deferTaskTypeInference : false) ?? false;
686
- if (!reviewing && bound === undefined && !delegation.isChild(payload.agent)) {
916
+ let requestIntentContext;
917
+ let requestIntent;
918
+ const activeRecord = bound ?? previous;
919
+ if (!reviewing && hasHumanInput(payload.messages) && !delegation.isChild(payload.agent) && intentTask.trim()) {
920
+ requestIntentContext = await requestIntentContextFor(payload, sessionId, boundSession, activeRecord, intentTask);
687
921
  const requestId = dshTurnRequestId({ dshSessionId: sessionId, turn: payload.turn });
922
+ const establishedConversation = profile?.taskType === 'chat' && !requestIntentContext?.activeGoal;
923
+ if (requestIntentContext && !establishedConversation) {
924
+ requestIntent = await classifyRequestIntent(decisions, requestId, requestIntentContext, payload.signal);
925
+ }
688
926
  const configuration = await decisions?.bind(requestId, payload.signal);
689
927
  // This profile is inherited from a previous turn, not a current user choice.
690
928
  // Let Laya assess new work, but retain the user's chat choice for an
691
929
  // ambiguous continuation instead of asking the same classification again.
692
930
  const explicit = advisoryType ?? (configuration?.provider === 'laya-coreml' && profile?.taskType !== 'chat' ? undefined : profile?.taskType);
693
- const classification = await classifyTaskForIntake(decisions, requestId, task, explicit, payload.signal);
931
+ const classification = await classifyTaskForIntake(decisions, requestId, intentTask, explicit, payload.signal);
694
932
  deferTaskTypeInference = classification.deferInference;
695
933
  if (classification.taskType)
696
934
  profile = { ...profile, taskType: classification.taskType };
697
935
  else if (deferTaskTypeInference && profile)
698
936
  profile = { ...profile, taskType: null };
699
937
  }
938
+ else if (activeRecord) {
939
+ requestIntent = activeRecord.requestIntent;
940
+ requestIntentContext = activeRecord.requestIntentContext;
941
+ }
942
+ if (suppliedIntent) {
943
+ if (!requestIntentContext)
944
+ throw new KiokukoError('CONFLICT', 'request_intent_context_unavailable');
945
+ const rebound = rebindRequestIntentDecision(suppliedIntent, requestIntentContext);
946
+ if (!rebound)
947
+ throw new KiokukoError('CONFLICT', 'request_intent_evidence_stale');
948
+ if (requestIntent?.source === 'decision-provider' && requestIntent.relation !== 'unknown' && requestIntent.action !== 'unknown'
949
+ && (requestIntent.relation !== rebound.relation || requestIntent.action !== rebound.action)) {
950
+ throw new KiokukoError('CONFLICT', 'request_intent_provider_conflict');
951
+ }
952
+ requestIntent = requestIntent?.source === 'decision-provider' && requestIntent.relation !== 'unknown' && requestIntent.action !== 'unknown'
953
+ ? { ...requestIntent, evidence: rebound.evidence } : rebound;
954
+ }
955
+ const pendingDiscussion = isContinuationReference(intentTask)
956
+ && await hasPendingExecutionDiscussion(sessionId, cwd);
957
+ const establishedConversation = profile?.taskType === 'chat' && !requestIntentContext?.activeGoal;
958
+ if (!interpretationOnly && intentTask.trim() && !pendingDiscussion && !establishedConversation) {
959
+ const clarification = executionClarification(intentTask, requestIntent, requestIntentContext);
960
+ if (clarification)
961
+ throw new KiokukoError('VALIDATION_ERROR', clarification);
962
+ }
963
+ const legacyTaskType = advisoryType !== undefined && advisoryType !== 'chat'
964
+ && !requestIntentContext?.activeGoal && (requestIntentContext?.proposals.length ?? 0) === 0;
965
+ if (requestIntent?.source === 'unresolved' && legacyTaskType)
966
+ requestIntent = undefined;
967
+ if (requestIntent?.source !== 'unresolved') {
968
+ const intentType = requestIntentTaskType(requestIntent, profile?.taskType ?? advisoryType);
969
+ if (intentType !== undefined)
970
+ profile = { ...profile, taskType: intentType };
971
+ }
700
972
  // Coding Lisp choice belongs to the admitted Goal, after all criteria exist.
701
973
  // Task intake never starts Lisp or interrupts an explanation with a mode question.
702
974
  const catalog = await capabilityCatalog(skills, tools, {
@@ -715,6 +987,9 @@ export function createAdmission({ native, skills, tools, userQuestions, sessions
715
987
  cwd,
716
988
  ...(profile === undefined ? {} : { profileHints: profile }),
717
989
  ...(deferTaskTypeInference ? { deferTaskTypeInference: true } : {}),
990
+ ...(requestIntent === undefined ? {} : { requestIntent }),
991
+ ...(requestIntentContext === undefined ? {} : { requestIntentContext }),
992
+ memoryOwner: memoryOwnerKind(requestIntent, profile?.taskType),
718
993
  capabilities: catalog,
719
994
  signal: payload.signal,
720
995
  };
@@ -722,6 +997,13 @@ export function createAdmission({ native, skills, tools, userQuestions, sessions
722
997
  return {
723
998
  gate,
724
999
  mapPreStep,
1000
+ interpret: async (payload) => {
1001
+ const event = await mapPreStep(payload, undefined, undefined, true);
1002
+ if (!event.requestIntentContext || !event.requestIntent)
1003
+ return undefined;
1004
+ return { context: event.requestIntentContext, decision: event.requestIntent };
1005
+ },
1006
+ refreshMemoryOwner,
725
1007
  clear: () => { continuedTurns.clear(); resumedTurns.clear(); },
726
1008
  };
727
1009
  }