@0xmaxma/claude-gateway 2.0.13 → 2.0.14
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/config.template.json +1 -1
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +2 -25
- package/dist/agent/runner.js.map +1 -1
- package/dist/build-provenance.json +4 -4
- package/dist/orchestration/cli-skills.d.ts +5 -0
- package/dist/orchestration/cli-skills.d.ts.map +1 -1
- package/dist/orchestration/cli-skills.js +63 -0
- package/dist/orchestration/cli-skills.js.map +1 -1
- package/dist/orchestration/config.d.ts +8 -0
- package/dist/orchestration/config.d.ts.map +1 -1
- package/dist/orchestration/config.js +4 -2
- package/dist/orchestration/config.js.map +1 -1
- package/dist/orchestration/conversation-intake.d.ts +1 -1
- package/dist/orchestration/conversation-intake.d.ts.map +1 -1
- package/dist/orchestration/conversation-intake.js +6 -5
- package/dist/orchestration/conversation-intake.js.map +1 -1
- package/dist/orchestration/input-batch.d.ts +16 -0
- package/dist/orchestration/input-batch.d.ts.map +1 -0
- package/dist/orchestration/input-batch.js +59 -0
- package/dist/orchestration/input-batch.js.map +1 -0
- package/dist/orchestration/runtime.d.ts +2 -0
- package/dist/orchestration/runtime.d.ts.map +1 -1
- package/dist/orchestration/runtime.js +79 -16
- package/dist/orchestration/runtime.js.map +1 -1
- package/dist/orchestration/skills.d.ts +6 -0
- package/dist/orchestration/skills.d.ts.map +1 -1
- package/dist/orchestration/skills.js +9 -2
- package/dist/orchestration/skills.js.map +1 -1
- package/package.json +1 -1
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.AgentOrchestrationRuntime = exports.GATEWAY_TASK_INSTRUCTIONS = void 0;
|
|
4
4
|
exports.readRequestDirective = readRequestDirective;
|
|
5
5
|
exports.agentHandoffDirective = agentHandoffDirective;
|
|
6
|
+
const input_batch_1 = require("./input-batch");
|
|
6
7
|
const computer_prompt_1 = require("./computer-prompt");
|
|
7
8
|
const control_notification_1 = require("./control-notification");
|
|
8
9
|
const computer_choice_ids_1 = require("../automation/computer-choice-ids");
|
|
@@ -1129,12 +1130,18 @@ class AgentOrchestrationRuntime {
|
|
|
1129
1130
|
mailboxCursor = Number(row.mailbox_row);
|
|
1130
1131
|
if (this.active.size >= this.config.conversation.maxActiveSessions)
|
|
1131
1132
|
break;
|
|
1132
|
-
|
|
1133
|
-
|
|
1133
|
+
// Batch members share the row's conversation and session, so admission is
|
|
1134
|
+
// decided before scanning later rows for the batch.
|
|
1135
|
+
const head = JSON.parse(String(row.ingress_json));
|
|
1136
|
+
if (!head.scope || !head.capabilities || this.active.has(head.scope.agentSessionId))
|
|
1134
1137
|
continue;
|
|
1135
|
-
if (visitedSessions.has(
|
|
1138
|
+
if (visitedSessions.has(head.scope.agentSessionId))
|
|
1139
|
+
continue;
|
|
1140
|
+
visitedSessions.add(head.scope.agentSessionId);
|
|
1141
|
+
const batch = (0, input_batch_1.inputBatch)(this.store, row, this.batchDebounceMs(), this.config.conversation.inputMaxWaitMs);
|
|
1142
|
+
const input = batch.input;
|
|
1143
|
+
if (Date.now() < batch.readyAt)
|
|
1136
1144
|
continue;
|
|
1137
|
-
visitedSessions.add(input.scope.agentSessionId);
|
|
1138
1145
|
const scope = (0, provider_scope_1.resolveProviderScope)(this.agent, this.gateway, input.model, 'agent');
|
|
1139
1146
|
const waiting = this.providerAdmission.inspect(scope, this.config.providerAdmission);
|
|
1140
1147
|
if (waiting) {
|
|
@@ -1145,7 +1152,7 @@ class AgentOrchestrationRuntime {
|
|
|
1145
1152
|
}
|
|
1146
1153
|
// A persisted input retains the authenticated scope and model from ingress.
|
|
1147
1154
|
// Recovery can repeat inference, but committed tool receipts remain fenced.
|
|
1148
|
-
const result = this.send({ ...input, acceptedInputId: String(
|
|
1155
|
+
const result = this.send({ ...input, acceptedInputId: String(batch.last.id) }, input.capabilities, { inputIds: batch.inputIds, timeoutMs: this.config.conversation.maxDecisionDurationMs, model: input.model, onTool: event => this.inputTools.get(String(row.id))?.(event), onText: text => {
|
|
1149
1156
|
const responseId = this.responseIdForInput(String(row.id));
|
|
1150
1157
|
if (input.modality !== 'live_voice' && responseId) {
|
|
1151
1158
|
try {
|
|
@@ -1154,13 +1161,26 @@ class AgentOrchestrationRuntime {
|
|
|
1154
1161
|
catch { /* slow audio cannot stall inference */ }
|
|
1155
1162
|
}
|
|
1156
1163
|
} });
|
|
1157
|
-
void result.then(text =>
|
|
1158
|
-
|
|
1164
|
+
void result.then(text => {
|
|
1165
|
+
// Superseded, uncommitted inputs remain durable and their callers wait
|
|
1166
|
+
// for the replacement decision rather than receiving an empty answer.
|
|
1167
|
+
for (const id of batch.inputIds)
|
|
1168
|
+
if (this.store.get('SELECT status FROM conversation_inputs WHERE id=?', id)?.status === 'handled') {
|
|
1169
|
+
this.deferred.get(id)?.resolve(text);
|
|
1170
|
+
this.deferred.delete(id);
|
|
1171
|
+
}
|
|
1172
|
+
}, error => { for (const id of batch.inputIds) {
|
|
1173
|
+
this.deferred.get(id)?.reject(error);
|
|
1174
|
+
this.deferred.delete(id);
|
|
1175
|
+
} });
|
|
1159
1176
|
}
|
|
1160
1177
|
if (this.active.size >= this.config.conversation.maxActiveSessions)
|
|
1161
1178
|
break mailbox;
|
|
1162
1179
|
}
|
|
1163
1180
|
}
|
|
1181
|
+
batchDebounceMs() {
|
|
1182
|
+
return this.config.conversation.semanticIntake ? this.config.conversation.inputDebounceMs : 0;
|
|
1183
|
+
}
|
|
1164
1184
|
async run(input, capabilities, options) {
|
|
1165
1185
|
const sessionId = input.scope.agentSessionId;
|
|
1166
1186
|
const channelTts = this.telegramVoices.settings((0, store_1.channelVoiceKey)(input.scope.source, input.scope.chatId, input.scope.threadKey));
|
|
@@ -1210,7 +1230,7 @@ class AgentOrchestrationRuntime {
|
|
|
1210
1230
|
this.store.run("UPDATE conversations SET status='draining' WHERE id=?", receipt.conversationId);
|
|
1211
1231
|
this.seenSessions.add(sessionId);
|
|
1212
1232
|
this.questionControls.tick();
|
|
1213
|
-
const decision = this.decisions.begin(receipt.conversationId, input.scope.principalId, [receipt.inputId], input.requestId);
|
|
1233
|
+
const decision = this.decisions.begin(receipt.conversationId, input.scope.principalId, options.inputIds ?? [receipt.inputId], input.requestId);
|
|
1214
1234
|
active.decision = decision;
|
|
1215
1235
|
if (questionReview) {
|
|
1216
1236
|
// Notifications may arrive after this internal input was queued. A
|
|
@@ -1277,9 +1297,9 @@ class AgentOrchestrationRuntime {
|
|
|
1277
1297
|
const semantic = this.config.conversation.semanticIntake && !active.notification && !liveControl;
|
|
1278
1298
|
const prepared = semantic ? this.intake.context(receipt.conversationId, input.scope.principalId, String(admitted.binding_id)) : undefined;
|
|
1279
1299
|
const recoveryInputId = input.ingressKey?.startsWith('intake-recovery:') ? input.ingressKey.slice('intake-recovery:'.length) : undefined;
|
|
1280
|
-
const preparedInputIds = [...new Set([...(prepared?.inputIds ?? []), ...(recoveryInputId ? [recoveryInputId] : [])])];
|
|
1300
|
+
const preparedInputIds = [...new Set([...(prepared?.inputIds ?? []), ...(!recoveryInputId && (prepared?.inputIds?.length || decision.inputIds.length > 1) ? decision.inputIds : []), ...(recoveryInputId ? [recoveryInputId] : [])])];
|
|
1281
1301
|
const preparedInputs = preparedInputIds.length ? this.store.all(`SELECT id,text,attachment_refs_json,ingress_json FROM conversation_inputs
|
|
1282
|
-
WHERE conversation_id=? AND principal_id=? AND binding_id=? AND id IN (SELECT value FROM json_each(?))`, receipt.conversationId, input.scope.principalId, admitted.binding_id, JSON.stringify(preparedInputIds)) : [];
|
|
1302
|
+
WHERE conversation_id=? AND principal_id=? AND binding_id=? AND id IN (SELECT value FROM json_each(?)) ORDER BY input_seq`, receipt.conversationId, input.scope.principalId, admitted.binding_id, JSON.stringify(preparedInputIds)) : [];
|
|
1283
1303
|
const preparedRefs = preparedInputs.flatMap(row => JSON.parse(String(row.attachment_refs_json)));
|
|
1284
1304
|
if (preparedRefs.length)
|
|
1285
1305
|
input = { ...input, attachmentIds: [...new Set([...(input.attachmentIds ?? []), ...preparedRefs])] };
|
|
@@ -1314,14 +1334,18 @@ class AgentOrchestrationRuntime {
|
|
|
1314
1334
|
}
|
|
1315
1335
|
// Immutable source IDs remain available to workers. Only the model delivery
|
|
1316
1336
|
// is incremental; full canonical text and attachment references are retained.
|
|
1317
|
-
|
|
1337
|
+
// Unresolved execution is an obligation, not optional cached context. Replay
|
|
1338
|
+
// it on follow-up/recovery even when this CLI has seen it before.
|
|
1339
|
+
const replayPending = Boolean(prepared?.deferredDispatch || recoveryInputId);
|
|
1340
|
+
const freshPreparedInputs = preparedInputs.filter(row => row.id !== receipt.inputId && (replayPending || !contextPlan.includes('materials', String(row.id))));
|
|
1318
1341
|
for (const row of freshPreparedInputs)
|
|
1319
1342
|
contextPlan.mark('materials', String(row.id), true);
|
|
1320
1343
|
contextPlan.mark('materials', receipt.inputId, true);
|
|
1321
1344
|
const intakeValue = (value) => value ? { mode: value.mode, deferredDispatch: value.deferredDispatch,
|
|
1322
1345
|
preparation: value.preparation, inputIds: value.inputIds, task_id: value.task_id, resolution: value.resolution } : null;
|
|
1323
1346
|
const pendingState = intakeValue(prepared);
|
|
1324
|
-
const
|
|
1347
|
+
const pendingDelta = contextPlan.select('intake', [{ id: 'pending', value: pendingState }], row => row.id);
|
|
1348
|
+
const pendingChanges = replayPending ? [{ id: 'pending', value: pendingState }] : pendingDelta;
|
|
1325
1349
|
if (!semantic && input.skill && input.modality !== 'live_voice' && !channelSpeech && !visualInput.images.length && !visualInput.unavailable.length) {
|
|
1326
1350
|
const task = this.tasks.spawn({ ...capabilities, ...receipt, ...decision, principalId: input.scope.principalId,
|
|
1327
1351
|
model: options.model ?? this.agent.claude.model, actionId: `skill:${receipt.inputId}` }, {
|
|
@@ -1469,7 +1493,7 @@ class AgentOrchestrationRuntime {
|
|
|
1469
1493
|
return;
|
|
1470
1494
|
if (!intakeChoice || intakeChoice.mode === 'wait' || intakeChoice.mode === 'resolve' || !acknowledgementReady)
|
|
1471
1495
|
throw new types_2.OrchestrationError('ACKNOWLEDGEMENT_REQUIRED');
|
|
1472
|
-
if (tool === 'task_spawn' && preparedInputs.length)
|
|
1496
|
+
if (tool === 'task_spawn' && !args.gateway_target && preparedInputs.length)
|
|
1473
1497
|
args.context_refs = [...new Set([...(Array.isArray(args.context_refs) ? args.context_refs : []), ...preparedRefs, ...preparedInputs.map(row => String(row.id))])];
|
|
1474
1498
|
if (intakeChoice.mode === 'update' && (tool === 'task_spawn' || args.task_id !== intakeChoice.task_id)) {
|
|
1475
1499
|
const attempt = attemptedTaskActions.get(actionId);
|
|
@@ -1663,14 +1687,18 @@ class AgentOrchestrationRuntime {
|
|
|
1663
1687
|
await this.flushHistory();
|
|
1664
1688
|
return display;
|
|
1665
1689
|
}
|
|
1690
|
+
let unsafeToReplay = false;
|
|
1666
1691
|
agentSession.on('output', (0, tool_activity_1.toolActivity)(event => {
|
|
1692
|
+
if (event.type === 'tool_use' && !/^(StructuredOutput|mcp__gateway__(conversation_intake|capabilities_list|memory_get|memory_search|task_status))$/.test(event.name))
|
|
1693
|
+
unsafeToReplay = true;
|
|
1667
1694
|
this.store.transaction(() => this.store.appendEvent(receipt.conversationId, internalReview ? 'progress.review.tool' : 'tool.activity', { ...event, responseId: decision.responseId, role: 'agent' }));
|
|
1668
1695
|
if (!internalReview)
|
|
1669
1696
|
options.onTool?.(event);
|
|
1670
1697
|
}));
|
|
1698
|
+
const bufferChat = semantic && this.config.conversation.inputDebounceMs > 0 && (0, input_batch_1.batchableInput)(input);
|
|
1671
1699
|
let rawDisplay = '', structuredStarted = false;
|
|
1672
1700
|
const displayChunk = (chunk) => {
|
|
1673
|
-
if (internalReview || questionReview)
|
|
1701
|
+
if (internalReview || questionReview || bufferChat || recoveryInputId)
|
|
1674
1702
|
return; // Buffer until the notify/silence decision is final.
|
|
1675
1703
|
if (semantic && (intakeChoice?.mode === 'wait' || intakeDeferred || acknowledgementId))
|
|
1676
1704
|
return;
|
|
@@ -1726,6 +1754,28 @@ class AgentOrchestrationRuntime {
|
|
|
1726
1754
|
}
|
|
1727
1755
|
catch { /* Provider bookkeeping cannot invalidate a completed inference. */ }
|
|
1728
1756
|
}
|
|
1757
|
+
const committedAction = this.store.get('SELECT action_id FROM task_commands WHERE decision_id=? LIMIT 1', decision.decisionId);
|
|
1758
|
+
const supersededCount = Number(this.store.get("SELECT count(*) n FROM conversation_events WHERE conversation_id=? AND type='response.superseded' AND json_extract(payload_json,'$.payload.inputId')=?", receipt.conversationId, decision.inputIds[0])?.n ?? 0);
|
|
1759
|
+
// Supersede only when the batcher would now absorb newer input into this
|
|
1760
|
+
// batch; an incompatible follow-up gets its own turn without re-inference.
|
|
1761
|
+
const batchGrows = () => {
|
|
1762
|
+
const first = this.store.get('SELECT * FROM conversation_inputs WHERE id=?', decision.inputIds[0]);
|
|
1763
|
+
return !!first && (0, input_batch_1.inputBatch)(this.store, first, this.batchDebounceMs(), this.config.conversation.inputMaxWaitMs, decision.inputIds)
|
|
1764
|
+
.inputIds.some(id => !decision.inputIds.includes(id));
|
|
1765
|
+
};
|
|
1766
|
+
if (bufferChat && newerInputPending() && batchGrows() && !response.interrupted && !active.stopping && !acknowledgementId && !committedAction && !unsafeToReplay && supersededCount < 2) {
|
|
1767
|
+
this.store.compose(() => {
|
|
1768
|
+
this.decisions.finish(decision, '', 'interrupted', undefined, false);
|
|
1769
|
+
for (const id of decision.inputIds)
|
|
1770
|
+
this.store.run("UPDATE conversation_inputs SET status='accepted' WHERE id=?", id);
|
|
1771
|
+
this.store.appendEvent(receipt.conversationId, 'response.superseded', { responseId: decision.responseId, inputId: decision.inputIds[0], inputIds: decision.inputIds });
|
|
1772
|
+
});
|
|
1773
|
+
// No user-visible answer or task action was committed. Re-read canonical
|
|
1774
|
+
// inputs with the newer messages; do not checkpoint discarded context.
|
|
1775
|
+
contextPlan.invalidate();
|
|
1776
|
+
await this.flushHistory();
|
|
1777
|
+
return '';
|
|
1778
|
+
}
|
|
1729
1779
|
if (!response.interrupted) {
|
|
1730
1780
|
// A completed CLI turn has consumed its tool results. Failed/interrupted
|
|
1731
1781
|
// turns never advance this checkpoint, so committed receipts can recover.
|
|
@@ -1811,7 +1861,20 @@ class AgentOrchestrationRuntime {
|
|
|
1811
1861
|
: 'The requested task was not started or updated. Please try again.';
|
|
1812
1862
|
surfaces.spoken = '';
|
|
1813
1863
|
}
|
|
1814
|
-
const
|
|
1864
|
+
const dispatchCommitted = this.store.get("SELECT action_id FROM task_commands WHERE decision_id=? AND command_type IN ('spawn','update','answer') LIMIT 1", decision.decisionId);
|
|
1865
|
+
if (semantic && !response.interrupted && !active.stopping && intakeChoice && ['ready', 'update'].includes(intakeChoice.mode) && !dispatchCommitted)
|
|
1866
|
+
this.intake.deferDispatch(receipt.inputId);
|
|
1867
|
+
const unresolved = semantic && this.intake.context(receipt.conversationId, input.scope.principalId, String(admitted.binding_id));
|
|
1868
|
+
const recoveryFailed = Boolean(recoveryInputId && unresolved?.deferredDispatch && unresolved.mode !== 'wait' && !response.interrupted && !active.stopping && !newerInputPending());
|
|
1869
|
+
if (recoveryFailed) {
|
|
1870
|
+
const thai = /[\u0E00-\u0E7F]/.test(input.text + String(unresolved && unresolved.preparation) + preparedInputs.map(row => row.text).join('') + response.text);
|
|
1871
|
+
surfaces.display = thai
|
|
1872
|
+
? 'ระบบยังจัดการคำขอค้างไม่สำเร็จ: agent จบการกู้คืนโดยไม่ได้ยืนยันว่างานถูกส่งต่อหรือปิดเรื่องแล้ว คำขอเดิมยังถูกเก็บไว้และยังไม่ถือว่าเสร็จค่ะ'
|
|
1873
|
+
: 'The agent ended recovery without confirming that the pending request was dispatched or resolved. The request is still retained and is not complete.';
|
|
1874
|
+
surfaces.spoken = '';
|
|
1875
|
+
this.store.transaction(() => this.store.appendEvent(receipt.conversationId, 'response.dispatch_unresolved', { responseId: decision.responseId, inputId: receipt.inputId }));
|
|
1876
|
+
}
|
|
1877
|
+
const intakeSilent = !recoveryFailed && semantic && !uncommittedDispatch && (intakeChoice?.mode === 'wait' || intakeDeferred || (acknowledgementId && unresolved?.deferredDispatch && !dispatchCommitted) || (acknowledgementId && this.store.get("SELECT action_id FROM task_commands WHERE decision_id=? AND command_type IN ('spawn','update','answer') LIMIT 1", decision.decisionId)));
|
|
1815
1878
|
if (intakeSilent) {
|
|
1816
1879
|
// A receipt/preparation turn has not reported older task results. Keep
|
|
1817
1880
|
// their notifications (and attachments) available to the next report.
|
|
@@ -1831,7 +1894,7 @@ class AgentOrchestrationRuntime {
|
|
|
1831
1894
|
for (const image of endScreenshots) {
|
|
1832
1895
|
(0, computer_end_screenshot_1.attachComputerEndScreenshot)(this.store, (0, path_1.join)(this.agent.workspace, '../..'), { ...image, responseId: decision.responseId, principalId: input.scope.principalId, conversationId: receipt.conversationId });
|
|
1833
1896
|
}
|
|
1834
|
-
this.decisions.finish(decision, display, response.interrupted ? 'interrupted' : 'completed', channelSpeech && !silent ? taskSpeech || surfaces.spoken : undefined, !active.stopping && !silent);
|
|
1897
|
+
this.decisions.finish(decision, display, response.interrupted ? 'interrupted' : recoveryFailed ? 'failed' : 'completed', channelSpeech && !silent ? taskSpeech || surfaces.spoken : undefined, !active.stopping && !silent);
|
|
1835
1898
|
if (active.notification && !response.interrupted) {
|
|
1836
1899
|
const batch = this.store.get('SELECT COUNT(*)-COUNT(DISTINCT task_id) n FROM notifications WHERE decision_id=?', decision.decisionId);
|
|
1837
1900
|
try {
|