@ngockhoale/ukit 3.4.30 → 3.4.31
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/CHANGELOG.md +13 -0
- package/manifests/documentation.yaml +12 -0
- package/package.json +1 -1
- package/src/core/executionContracts.js +32 -0
- package/src/decision/client.js +15 -0
- package/src/decision/protocol.js +12 -14
- package/src/index/routeResolver.js +2 -2
- package/src/index/taskRouting.js +3 -1
- package/template_project/.claude/hooks/record-execution.mjs +1 -3
- package/template_project/.claude/hooks/skill-router.sh +82 -9
- package/template_project/.claude/settings.json +22 -0
- package/template_project/.claude/ukit/index/fix-loop-escalation.mjs +10 -4
- package/template_project/.claude/ukit/index/review-verdict.mjs +14 -4
- package/template_project/.claude/ukit/index/route-resolver.mjs +34 -2
- package/template_project/.claude/ukit/index/route-task.mjs +35 -7
- package/template_project/.claude/ukit/index/sidecar-decision.mjs +3 -59
- package/template_project/.claude/ukit/index/unic-decision.mjs +27 -14
- package/template_project/.claude/ukit/runtime/cron-operation-adapter.mjs +43 -0
- package/template_project/.claude/ukit/runtime/execution-ledger.mjs +160 -33
- package/template_project/.claude/ukit/runtime/loop-state.mjs +1 -1
- package/template_project/.claude/ukit/runtime/operation-evidence.mjs +87 -0
- package/template_project/.omp/hooks/pre/ukit-bridge.js +8 -1
- package/template_project/AGENTS.md +1 -1
- package/template_project/CLAUDE.md +1 -1
- package/template_project/instructions/core.md +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { classifyTaskSemantics, applyTaskSemantics } from './route-resolver.mjs';
|
|
2
3
|
import fs from 'node:fs/promises';
|
|
3
4
|
// `fsSync` aliases the SAME fs module — used ONLY by the process.on('exit')
|
|
4
5
|
// audit-sidecar fold below (event loop is gone; async cannot run) and by the
|
|
@@ -799,6 +800,7 @@ function buildRouteHotCacheState(sharedState = {}) {
|
|
|
799
800
|
delete routingContext.lastExplicitUserPromptText;
|
|
800
801
|
delete routingContext.commandText;
|
|
801
802
|
delete state.sessionId;
|
|
803
|
+
delete state.taskInstanceId;
|
|
802
804
|
state.routingContext = routingContext;
|
|
803
805
|
return state;
|
|
804
806
|
}
|
|
@@ -895,6 +897,7 @@ function buildReducedRouteState({ input, reason }) {
|
|
|
895
897
|
source: 'task-route',
|
|
896
898
|
requestKey: null,
|
|
897
899
|
sessionId: input.sessionId ?? null,
|
|
900
|
+
taskInstanceId: input.taskInstanceId || undefined,
|
|
898
901
|
});
|
|
899
902
|
}
|
|
900
903
|
|
|
@@ -911,7 +914,7 @@ export async function routeTask(input = {}, { signal = null, deadlineMs = null,
|
|
|
911
914
|
const absoluteRoot = path.resolve(input.rootDir ?? process.cwd());
|
|
912
915
|
const budget = createRouteBudget({ signal, deadlineMs });
|
|
913
916
|
const hotCache = cache ?? createDefaultRouteHotCache({ rootDir: absoluteRoot });
|
|
914
|
-
const normalizedInput = { ...input, rootDir: absoluteRoot };
|
|
917
|
+
const normalizedInput = { ...input, rootDir: absoluteRoot, taskInstanceId: input.taskInstanceId || `task-${crypto.randomBytes(16).toString('hex')}` };
|
|
915
918
|
const baseMeta = {
|
|
916
919
|
cacheHit: false,
|
|
917
920
|
reduced: false,
|
|
@@ -929,7 +932,7 @@ export async function routeTask(input = {}, { signal = null, deadlineMs = null,
|
|
|
929
932
|
elapsedMs: Date.now() - budget.startedAt,
|
|
930
933
|
ioReads: budget.ioReads,
|
|
931
934
|
};
|
|
932
|
-
return { ...state, cacheHit: routeMeta.cacheHit, routeMeta };
|
|
935
|
+
return { ...state, taskInstanceId: normalizedInput.taskInstanceId || state.taskInstanceId || `task-${crypto.randomBytes(16).toString('hex')}`, cacheHit: routeMeta.cacheHit, routeMeta };
|
|
933
936
|
};
|
|
934
937
|
const reduce = (reason) => finish(
|
|
935
938
|
buildReducedRouteState({ input: normalizedInput, reason }),
|
|
@@ -1079,6 +1082,7 @@ export async function routeTask(input = {}, { signal = null, deadlineMs = null,
|
|
|
1079
1082
|
requestKey,
|
|
1080
1083
|
indexGeneratedAtMs,
|
|
1081
1084
|
sessionId: normalizedInput.sessionId ?? null,
|
|
1085
|
+
taskInstanceId: normalizedInput.taskInstanceId || undefined,
|
|
1082
1086
|
});
|
|
1083
1087
|
|
|
1084
1088
|
// Phase 3 — store under a POST-route fingerprint so the next identical call (whose own
|
|
@@ -1126,7 +1130,12 @@ async function main() {
|
|
|
1126
1130
|
const sessionId = normalizeSessionId(readFlagValue(args, '--session-id'));
|
|
1127
1131
|
const previousStateOwnershipBlocked = hasRouteStateOwner(persistedPreviousState)
|
|
1128
1132
|
&& !isRouteStateCompatible(persistedPreviousState, sessionId);
|
|
1129
|
-
const
|
|
1133
|
+
const sessionSidecar = previousStateOwnershipBlocked && sessionId
|
|
1134
|
+
? await readJson(path.join(rootDir, '.ukit', 'storage', 'cache', 'route-state', `${sessionId.replace(/[^a-zA-Z0-9._-]/g, '_').slice(0, 96) || 'default'}.json`), {})
|
|
1135
|
+
: {};
|
|
1136
|
+
const previousState = previousStateOwnershipBlocked
|
|
1137
|
+
? (isRouteStateCompatible(sessionSidecar, sessionId) && sessionSidecar.sessionId === sessionId ? sessionSidecar : {})
|
|
1138
|
+
: persistedPreviousState;
|
|
1130
1139
|
const escalationConfig = await readMergedConfig(rootDir);
|
|
1131
1140
|
const targetFile = readFlagValue(args, '--target');
|
|
1132
1141
|
const taskType = readFlagValue(args, '--type');
|
|
@@ -1144,7 +1153,7 @@ async function main() {
|
|
|
1144
1153
|
const commandNamespace = adapter === 'codex'
|
|
1145
1154
|
? '.codex'
|
|
1146
1155
|
: (adapter === 'antigravity' ? '.antigravity' : '.claude');
|
|
1147
|
-
|
|
1156
|
+
let promptText = args
|
|
1148
1157
|
.filter((arg, index) => !isFlagOrValue(args, index))
|
|
1149
1158
|
.join(' ')
|
|
1150
1159
|
.trim();
|
|
@@ -1155,6 +1164,15 @@ async function main() {
|
|
|
1155
1164
|
return;
|
|
1156
1165
|
}
|
|
1157
1166
|
|
|
1167
|
+
const continuingTask = Boolean(previousState.taskInstanceId && lastPrompt
|
|
1168
|
+
&& /^(?:continue|resume|keep going|proceed|tiep tuc)(?:\s+(?:the task|this task|previous task))?[.!]?$/i.test(
|
|
1169
|
+
promptText.normalize('NFD').replace(/[̀-ͯ]/g, '').trim()));
|
|
1170
|
+
const taskInstanceId = readFlagValue(args, '--task-instance-id')
|
|
1171
|
+
|| (((['skill-router', 'skill-router-provisional'].includes(previousState.source)
|
|
1172
|
+
&& previousState.routingContext?.lastExplicitUserPromptText === promptText) || continuingTask)
|
|
1173
|
+
&& previousState.taskInstanceId
|
|
1174
|
+
? previousState.taskInstanceId : `task-${crypto.randomBytes(16).toString('hex')}`);
|
|
1175
|
+
if (continuingTask) promptText = lastPrompt;
|
|
1158
1176
|
const preparedRoute = await prepareTaskRoute({
|
|
1159
1177
|
rootDir,
|
|
1160
1178
|
promptText,
|
|
@@ -1249,7 +1267,12 @@ async function main() {
|
|
|
1249
1267
|
historySignals,
|
|
1250
1268
|
};
|
|
1251
1269
|
}
|
|
1252
|
-
|
|
1270
|
+
const sharedState = { ...reusableState, taskInstanceId, source: 'task-route', ts: Date.now() };
|
|
1271
|
+
if (canPersistRouteState) {
|
|
1272
|
+
try { await persistSharedRouteState(sharedStatePath, sharedState); }
|
|
1273
|
+
catch (error) { console.error(`route-task: state persist degraded — ${error?.message ?? error}`); }
|
|
1274
|
+
}
|
|
1275
|
+
printRouteState(sharedState);
|
|
1253
1276
|
return;
|
|
1254
1277
|
}
|
|
1255
1278
|
|
|
@@ -1331,6 +1354,7 @@ async function main() {
|
|
|
1331
1354
|
}
|
|
1332
1355
|
const sharedState = {
|
|
1333
1356
|
...cachedRouteState,
|
|
1357
|
+
taskInstanceId,
|
|
1334
1358
|
...(sessionId ? { sessionId } : {}),
|
|
1335
1359
|
source: 'task-route',
|
|
1336
1360
|
ts: Date.now(),
|
|
@@ -1391,6 +1415,7 @@ async function main() {
|
|
|
1391
1415
|
requestKey,
|
|
1392
1416
|
indexGeneratedAtMs,
|
|
1393
1417
|
sessionId,
|
|
1418
|
+
taskInstanceId,
|
|
1394
1419
|
});
|
|
1395
1420
|
if (canPersistRouteState) {
|
|
1396
1421
|
// Advisory persistence — an EACCES/ENOSPC under the lock must never lose
|
|
@@ -3775,11 +3800,12 @@ function buildRouteSummary({
|
|
|
3775
3800
|
executionScores,
|
|
3776
3801
|
executionCandidates,
|
|
3777
3802
|
});
|
|
3778
|
-
const executionContract = buildExecutionContract(executionMode);
|
|
3803
|
+
const executionContract = applyTaskSemantics(buildExecutionContract(executionMode), classifyTaskSemantics({ promptText: routingContext.lastExplicitUserPromptText || routingContext.promptText, targetFile: routingContext.targetFile }));
|
|
3779
3804
|
const continuousExecution = buildContinuousExecutionPolicy(autonomyLevel);
|
|
3780
3805
|
const tierLane = buildTierLane({ modelTier: executionContract?.modelTier ?? null });
|
|
3781
3806
|
const completionState = buildCompletionState({
|
|
3782
3807
|
executionMode,
|
|
3808
|
+
executionContract,
|
|
3783
3809
|
verificationRecommendation,
|
|
3784
3810
|
});
|
|
3785
3811
|
const continuationState = buildContinuationState({
|
|
@@ -5417,7 +5443,7 @@ function isFlagOrValue(argv, index) {
|
|
|
5417
5443
|
const arg = argv[index];
|
|
5418
5444
|
if (!arg.startsWith('--')) {
|
|
5419
5445
|
const prev = argv[index - 1];
|
|
5420
|
-
return Boolean(prev && ['--root', '--target', '--type', '--tool-command', '--last-prompt', '--session-id', '--adapter', '--transcript'].includes(prev));
|
|
5446
|
+
return Boolean(prev && ['--root', '--target', '--type', '--tool-command', '--last-prompt', '--session-id', '--task-instance-id', '--adapter', '--transcript'].includes(prev));
|
|
5421
5447
|
}
|
|
5422
5448
|
return true;
|
|
5423
5449
|
}
|
|
@@ -5550,6 +5576,7 @@ function createSharedRouteState({
|
|
|
5550
5576
|
source = 'task-route',
|
|
5551
5577
|
requestKey = null,
|
|
5552
5578
|
sessionId = null,
|
|
5579
|
+
taskInstanceId = `task-${crypto.randomBytes(16).toString('hex')}`,
|
|
5553
5580
|
}) {
|
|
5554
5581
|
const helpers = compactHelpers({
|
|
5555
5582
|
verificationRecommendation: route.verificationRecommendation ?? null,
|
|
@@ -5559,6 +5586,7 @@ function createSharedRouteState({
|
|
|
5559
5586
|
return {
|
|
5560
5587
|
...(sessionId ? { sessionId } : {}),
|
|
5561
5588
|
requestKey,
|
|
5589
|
+
taskInstanceId,
|
|
5562
5590
|
fingerprint: buildRouteStateFingerprint(route),
|
|
5563
5591
|
ts: Date.now(),
|
|
5564
5592
|
source,
|
|
@@ -393,65 +393,9 @@ export async function runSidecarDecision({ decision, context = {}, rootDir, home
|
|
|
393
393
|
fallbackPolicy: spec.fallbackPolicy,
|
|
394
394
|
};
|
|
395
395
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
const result = runAdapterBatch({
|
|
401
|
-
spec, context: ctx, rootDir: rootDir ?? process.cwd(), config: mergedConfig,
|
|
402
|
-
batchId: `sidecar-${decision}-${Date.now().toString(36)}`,
|
|
403
|
-
});
|
|
404
|
-
const modelAnswer = validModelAnswer(result, spec.decisionKey);
|
|
405
|
-
const outcomeClass = result?.status ?? 'unavailable';
|
|
406
|
-
const agreement = modelAnswer === null
|
|
407
|
-
? 'unknown'
|
|
408
|
-
: modelAnswer.toLowerCase() === String(deterministic).toLowerCase()
|
|
409
|
-
? 'agree'
|
|
410
|
-
: 'disagree';
|
|
411
|
-
|
|
412
|
-
if (outcomeClass === 'unavailable' || outcomeClass === 'unsupported' || outcomeClass === 'invalid') {
|
|
413
|
-
return {
|
|
414
|
-
...base,
|
|
415
|
-
answer: deterministic,
|
|
416
|
-
source: 'deterministic-fallback',
|
|
417
|
-
outcomeClass: 'unavailable',
|
|
418
|
-
modelAnswer,
|
|
419
|
-
agreement,
|
|
420
|
-
fallbackCode: result?.fallbackCode ?? outcomeClass,
|
|
421
|
-
};
|
|
422
|
-
}
|
|
423
|
-
if (stage === 'shadow') {
|
|
424
|
-
return {
|
|
425
|
-
...base,
|
|
426
|
-
answer: deterministic,
|
|
427
|
-
source: 'deterministic',
|
|
428
|
-
outcomeClass,
|
|
429
|
-
modelAnswer,
|
|
430
|
-
agreement,
|
|
431
|
-
fallbackCode: result?.fallbackCode ?? null,
|
|
432
|
-
};
|
|
433
|
-
}
|
|
434
|
-
// canary/default: valid model answer wins; invalid → deterministic fallback.
|
|
435
|
-
if (modelAnswer !== null) {
|
|
436
|
-
return {
|
|
437
|
-
...base,
|
|
438
|
-
answer: modelAnswer,
|
|
439
|
-
source: 'unic-decision',
|
|
440
|
-
outcomeClass,
|
|
441
|
-
modelAnswer,
|
|
442
|
-
agreement,
|
|
443
|
-
fallbackCode: result?.fallbackCode ?? null,
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
return {
|
|
447
|
-
...base,
|
|
448
|
-
answer: deterministic,
|
|
449
|
-
source: 'deterministic-fallback',
|
|
450
|
-
outcomeClass,
|
|
451
|
-
modelAnswer,
|
|
452
|
-
agreement,
|
|
453
|
-
fallbackCode: result?.fallbackCode ?? 'missing-answer',
|
|
454
|
-
};
|
|
396
|
+
// All six sidecar decisions have complete deterministic rules. A model vote
|
|
397
|
+
// cannot waive their constraints; skip inference rather than ask and clamp.
|
|
398
|
+
return { ...base, answer: deterministic, source: 'deterministic', outcomeClass: 'deterministic' };
|
|
455
399
|
}
|
|
456
400
|
|
|
457
401
|
// ---------------------------------------------------------------------------
|
|
@@ -83,23 +83,20 @@ export function encodeQuestion(question) {
|
|
|
83
83
|
description: 'true iff the proposition holds; interpreted as P(true).',
|
|
84
84
|
};
|
|
85
85
|
} else {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
value.minimum = 1;
|
|
91
|
-
value.maximum = legend.length;
|
|
92
|
-
value.description = `Ordered scale: ${legend
|
|
93
|
-
.map((label, i) => `${i + 1}=${label}`)
|
|
94
|
-
.join(', ')}.`;
|
|
86
|
+
if (!Array.isArray(candidates) || candidates.length === 0
|
|
87
|
+
|| candidates.some(label => typeof label !== 'string' || !label.trim())
|
|
88
|
+
|| new Set(candidates).size !== candidates.length) {
|
|
89
|
+
throw new TypeError('score requires unique non-empty ordinal labels.');
|
|
95
90
|
}
|
|
91
|
+
value = { type: 'string', enum: [...candidates] };
|
|
96
92
|
}
|
|
97
93
|
|
|
98
94
|
return {
|
|
99
95
|
type: 'function',
|
|
100
96
|
function: {
|
|
101
97
|
name: decisionKey,
|
|
102
|
-
description: String(instruction ?? '')
|
|
98
|
+
description: String(instruction ?? '') + (kind === 'score'
|
|
99
|
+
? `\nOrdered ordinal rubric: ${candidates.map((label, i) => `${i + 1}=${JSON.stringify(label)}`).join(', ')}. Choose exactly one label; do not return an expected numeric score.` : ''),
|
|
103
100
|
parameters: {
|
|
104
101
|
type: 'object',
|
|
105
102
|
properties: { value },
|
|
@@ -196,11 +193,12 @@ function validateValue(question, args) {
|
|
|
196
193
|
if (isFiniteNumber(value) && value >= 0 && value <= 1) return { value };
|
|
197
194
|
return 'missing-value';
|
|
198
195
|
}
|
|
199
|
-
//
|
|
200
|
-
if (!isFiniteNumber(value) || !Number.isInteger(value)) return 'missing-value';
|
|
196
|
+
// Decode an explicit choice losslessly; never reinterpret native expectations.
|
|
201
197
|
const legend = Array.isArray(question.candidates) ? question.candidates : [];
|
|
202
|
-
if (legend.length
|
|
203
|
-
|
|
198
|
+
if (!legend.length || new Set(legend).size !== legend.length) return 'out-of-enum';
|
|
199
|
+
if (typeof value !== 'string') return 'missing-value';
|
|
200
|
+
const ordinal = legend.indexOf(value);
|
|
201
|
+
return ordinal < 0 ? 'out-of-enum' : { value: ordinal + 1 };
|
|
204
202
|
}
|
|
205
203
|
|
|
206
204
|
export function parseBatchResponse(response, questions) {
|
|
@@ -834,6 +832,21 @@ export async function requestBatch(batch, {
|
|
|
834
832
|
return outcome('invalid', { fallbackCode: 'empty-batch' });
|
|
835
833
|
}
|
|
836
834
|
|
|
835
|
+
const maxBatch = dp.maxBatchQuestions ?? 8;
|
|
836
|
+
const maxEnum = dp.maxEnumLabels ?? 11;
|
|
837
|
+
if (![maxBatch, maxEnum].every(n => Number.isInteger(n) && n > 0)) {
|
|
838
|
+
return outcome('unsupported', { fallbackCode: 'invalid-decision-limits' });
|
|
839
|
+
}
|
|
840
|
+
if (questions.length > maxBatch) {
|
|
841
|
+
return outcome('unsupported', { fallbackCode: 'max-batch-questions' });
|
|
842
|
+
}
|
|
843
|
+
if (questions.some(q => Array.isArray(q.candidates) && q.candidates.length > maxEnum)) {
|
|
844
|
+
return outcome('unsupported', { fallbackCode: 'max-enum-labels' });
|
|
845
|
+
}
|
|
846
|
+
if (new Set(questions.map(q => q.decisionKey)).size !== questions.length) {
|
|
847
|
+
return outcome('invalid', { fallbackCode: 'duplicate-question' });
|
|
848
|
+
}
|
|
849
|
+
|
|
837
850
|
// Language classification covers state + question text; the English
|
|
838
851
|
// checkpoint is reachable only when the whole request proves English.
|
|
839
852
|
// Malformed batch input (cyclic state, non-serializable values, internal
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Capability adapters normalize native results; completion consumes their common receipt shape.
|
|
2
|
+
const ID = /^[a-f0-9-]{8,36}$/i;
|
|
3
|
+
function responseText(response) {
|
|
4
|
+
if (typeof response === 'string') return response.trim();
|
|
5
|
+
if (response?.content !== undefined && (!Array.isArray(response.content)
|
|
6
|
+
|| !response.content.every((c) => c?.type === 'text' && typeof c.text === 'string'))) return '';
|
|
7
|
+
if (typeof response?.output === 'string') return response.output.trim();
|
|
8
|
+
if (typeof response?.stdout === 'string') return response.stdout.trim();
|
|
9
|
+
if (Array.isArray(response?.content) && response.content.every((c) => c?.type === 'text' && typeof c.text === 'string')) {
|
|
10
|
+
return response.content.map((c) => c.text).join('\n').trim();
|
|
11
|
+
}
|
|
12
|
+
return '';
|
|
13
|
+
}
|
|
14
|
+
const capabilities = {
|
|
15
|
+
CronDelete: { domain: 'scheduled-job', role: 'mutation', normalize(input, response) {
|
|
16
|
+
const target = typeof input?.id === 'string' && ID.test(input.id) ? input.id.toLowerCase() : null;
|
|
17
|
+
return { target, accepted: Boolean(target && responseText(response) === `Cancelled ${input.id}`) };
|
|
18
|
+
} },
|
|
19
|
+
CronList: { domain: 'scheduled-job', role: 'state', normalize(_input, response) {
|
|
20
|
+
const body = response?.details && Object.hasOwn(response.details, 'jobs') ? response.details : response;
|
|
21
|
+
const structured = body != null && typeof body === 'object' && Object.hasOwn(body, 'jobs');
|
|
22
|
+
const jobs = Array.isArray(body?.jobs) ? body.jobs : null;
|
|
23
|
+
const ids = jobs && jobs.length <= 256 && jobs.every((job) => typeof job?.id === 'string' && ID.test(job.id))
|
|
24
|
+
? jobs.map((job) => job.id.toLowerCase()) : null;
|
|
25
|
+
const partial = [body, response].some((value) => value?.partial === true || value?.hasMore === true
|
|
26
|
+
|| Boolean(value?.nextCursor) || value?.complete === false);
|
|
27
|
+
const supportedContent = response?.content === undefined || (Array.isArray(response.content)
|
|
28
|
+
&& response.content.every((c) => c?.type === 'text' && typeof c.text === 'string'));
|
|
29
|
+
const valid = !partial && supportedContent
|
|
30
|
+
&& (structured ? ids !== null : /^No scheduled jobs\.?$/.test(responseText(response)));
|
|
31
|
+
return { target: null, state: 'absent', presentTargets: valid ? ids || [] : null, complete: valid, accepted: valid };
|
|
32
|
+
} },
|
|
33
|
+
};
|
|
34
|
+
export function cronOperationReceipt(toolName, input, response, transportSuccess, { sessionId, harness } = {}) {
|
|
35
|
+
const capability = capabilities[toolName];
|
|
36
|
+
if (!capability) return null;
|
|
37
|
+
const normalized = capability.normalize(input, response);
|
|
38
|
+
const { accepted, ...state } = normalized;
|
|
39
|
+
const scope = typeof sessionId === 'string' && sessionId.trim() ? `session:${sessionId.trim()}` : null;
|
|
40
|
+
return { kind: 'operation', success: transportSuccess === true && accepted && Boolean(scope),
|
|
41
|
+
transportSuccess: transportSuccess === true, harness,
|
|
42
|
+
operation: { domain: capability.domain, role: capability.role, scope, ...state } };
|
|
43
|
+
}
|