@adonis-agora/agent 0.60.0 → 0.61.0
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 +40 -0
- package/dist/configure.d.ts.map +1 -1
- package/dist/configure.js +2 -0
- package/dist/configure.js.map +1 -1
- package/dist/providers/agent_provider.d.ts.map +1 -1
- package/dist/providers/agent_provider.js +119 -2
- package/dist/providers/agent_provider.js.map +1 -1
- package/dist/src/action-proposal-approval.d.ts +26 -0
- package/dist/src/action-proposal-approval.d.ts.map +1 -0
- package/dist/src/action-proposal-approval.js +28 -0
- package/dist/src/action-proposal-approval.js.map +1 -0
- package/dist/src/action-proposal-discovery.d.ts +9 -0
- package/dist/src/action-proposal-discovery.d.ts.map +1 -0
- package/dist/src/action-proposal-discovery.js +20 -0
- package/dist/src/action-proposal-discovery.js.map +1 -0
- package/dist/src/action-proposal-executor.d.ts +37 -0
- package/dist/src/action-proposal-executor.d.ts.map +1 -0
- package/dist/src/action-proposal-executor.js +70 -0
- package/dist/src/action-proposal-executor.js.map +1 -0
- package/dist/src/action-proposal-outcome.d.ts +12 -0
- package/dist/src/action-proposal-outcome.d.ts.map +1 -0
- package/dist/src/action-proposal-outcome.js +86 -0
- package/dist/src/action-proposal-outcome.js.map +1 -0
- package/dist/src/action-proposal-preparation.d.ts +20 -0
- package/dist/src/action-proposal-preparation.d.ts.map +1 -0
- package/dist/src/action-proposal-preparation.js +27 -0
- package/dist/src/action-proposal-preparation.js.map +1 -0
- package/dist/src/action-proposal-receipt.d.ts +12 -0
- package/dist/src/action-proposal-receipt.d.ts.map +1 -0
- package/dist/src/action-proposal-receipt.js +4 -0
- package/dist/src/action-proposal-receipt.js.map +1 -0
- package/dist/src/action-proposal-runtime.d.ts +9 -0
- package/dist/src/action-proposal-runtime.d.ts.map +1 -0
- package/dist/src/action-proposal-runtime.js +34 -0
- package/dist/src/action-proposal-runtime.js.map +1 -0
- package/dist/src/action-proposal-service.d.ts +43 -0
- package/dist/src/action-proposal-service.d.ts.map +1 -0
- package/dist/src/action-proposal-service.js +132 -0
- package/dist/src/action-proposal-service.js.map +1 -0
- package/dist/src/action-proposal-text.d.ts +33 -0
- package/dist/src/action-proposal-text.d.ts.map +1 -0
- package/dist/src/action-proposal-text.js +51 -0
- package/dist/src/action-proposal-text.js.map +1 -0
- package/dist/src/action-proposal-transitions.d.ts +20 -0
- package/dist/src/action-proposal-transitions.d.ts.map +1 -0
- package/dist/src/action-proposal-transitions.js +311 -0
- package/dist/src/action-proposal-transitions.js.map +1 -0
- package/dist/src/action-proposal-worker.d.ts +31 -0
- package/dist/src/action-proposal-worker.d.ts.map +1 -0
- package/dist/src/action-proposal-worker.js +133 -0
- package/dist/src/action-proposal-worker.js.map +1 -0
- package/dist/src/ag-ui/adapter.d.ts.map +1 -1
- package/dist/src/ag-ui/adapter.js +33 -0
- package/dist/src/ag-ui/adapter.js.map +1 -1
- package/dist/src/ag-ui/frames.d.ts.map +1 -1
- package/dist/src/ag-ui/frames.js +1 -0
- package/dist/src/ag-ui/frames.js.map +1 -1
- package/dist/src/ag-ui/proposal-decision.d.ts +9 -0
- package/dist/src/ag-ui/proposal-decision.d.ts.map +1 -0
- package/dist/src/ag-ui/proposal-decision.js +23 -0
- package/dist/src/ag-ui/proposal-decision.js.map +1 -0
- package/dist/src/agent-deps-factory.d.ts +5 -0
- package/dist/src/agent-deps-factory.d.ts.map +1 -1
- package/dist/src/agent-deps-factory.js +9 -0
- package/dist/src/agent-deps-factory.js.map +1 -1
- package/dist/src/agent-deps.d.ts +4 -0
- package/dist/src/agent-deps.d.ts.map +1 -1
- package/dist/src/agent-deps.js.map +1 -1
- package/dist/src/agent-loop.d.ts +4 -0
- package/dist/src/agent-loop.d.ts.map +1 -1
- package/dist/src/agent-loop.js +372 -62
- package/dist/src/agent-loop.js.map +1 -1
- package/dist/src/agent-service.d.ts +42 -1
- package/dist/src/agent-service.d.ts.map +1 -1
- package/dist/src/agent-service.js +49 -1
- package/dist/src/agent-service.js.map +1 -1
- package/dist/src/ai-tool-ref.d.ts +2 -1
- package/dist/src/ai-tool-ref.d.ts.map +1 -1
- package/dist/src/ai-tool-ref.js +22 -2
- package/dist/src/ai-tool-ref.js.map +1 -1
- package/dist/src/chat-queue-service.d.ts.map +1 -1
- package/dist/src/chat-queue-service.js +2 -0
- package/dist/src/chat-queue-service.js.map +1 -1
- package/dist/src/client/chat-client.d.ts +12 -0
- package/dist/src/client/chat-client.d.ts.map +1 -1
- package/dist/src/client/chat-client.js +69 -1
- package/dist/src/client/chat-client.js.map +1 -1
- package/dist/src/client/sse.d.ts +15 -0
- package/dist/src/client/sse.d.ts.map +1 -1
- package/dist/src/client/sse.js +45 -0
- package/dist/src/client/sse.js.map +1 -1
- package/dist/src/define_config.d.ts +9 -0
- package/dist/src/define_config.d.ts.map +1 -1
- package/dist/src/define_config.js.map +1 -1
- package/dist/src/genui/factory.d.ts +8 -1
- package/dist/src/genui/factory.d.ts.map +1 -1
- package/dist/src/genui/factory.js +3 -1
- package/dist/src/genui/factory.js.map +1 -1
- package/dist/src/genui/index.d.ts.map +1 -1
- package/dist/src/genui/index.js +1 -0
- package/dist/src/genui/index.js.map +1 -1
- package/dist/src/index.d.ts +18 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +18 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/negotiated-tool-ui.d.ts +10 -0
- package/dist/src/negotiated-tool-ui.d.ts.map +1 -0
- package/dist/src/negotiated-tool-ui.js +53 -0
- package/dist/src/negotiated-tool-ui.js.map +1 -0
- package/dist/src/spi/action-proposal-outcome-store.d.ts +43 -0
- package/dist/src/spi/action-proposal-outcome-store.d.ts.map +1 -0
- package/dist/src/spi/action-proposal-outcome-store.js +2 -0
- package/dist/src/spi/action-proposal-outcome-store.js.map +1 -0
- package/dist/src/spi/action-proposal-store.d.ts +152 -0
- package/dist/src/spi/action-proposal-store.d.ts.map +1 -0
- package/dist/src/spi/action-proposal-store.js +2 -0
- package/dist/src/spi/action-proposal-store.js.map +1 -0
- package/dist/src/spi/action-proposal-worker-store.d.ts +17 -0
- package/dist/src/spi/action-proposal-worker-store.d.ts.map +1 -0
- package/dist/src/spi/action-proposal-worker-store.js +2 -0
- package/dist/src/spi/action-proposal-worker-store.js.map +1 -0
- package/dist/src/spi/agent-store.d.ts +5 -0
- package/dist/src/spi/agent-store.d.ts.map +1 -1
- package/dist/src/spi/agent-store.js.map +1 -1
- package/dist/src/spi/approval-policy.d.ts +3 -0
- package/dist/src/spi/approval-policy.d.ts.map +1 -1
- package/dist/src/spi/approval-policy.js +8 -1
- package/dist/src/spi/approval-policy.js.map +1 -1
- package/dist/src/spi/background-actor-resolver.d.ts +9 -0
- package/dist/src/spi/background-actor-resolver.d.ts.map +1 -0
- package/dist/src/spi/background-actor-resolver.js +2 -0
- package/dist/src/spi/background-actor-resolver.js.map +1 -0
- package/dist/src/spi/chat-queue.d.ts +3 -0
- package/dist/src/spi/chat-queue.d.ts.map +1 -1
- package/dist/src/spi/chat-queue.js.map +1 -1
- package/dist/src/spi/token-stream-sink.d.ts +9 -0
- package/dist/src/spi/token-stream-sink.d.ts.map +1 -1
- package/dist/src/spi/token-stream-sink.js.map +1 -1
- package/dist/src/spi/tool.d.ts +22 -0
- package/dist/src/spi/tool.d.ts.map +1 -1
- package/dist/src/sse.d.ts.map +1 -1
- package/dist/src/sse.js +6 -0
- package/dist/src/sse.js.map +1 -1
- package/dist/src/stores/lucid-action-proposal-store.d.ts +55 -0
- package/dist/src/stores/lucid-action-proposal-store.d.ts.map +1 -0
- package/dist/src/stores/lucid-action-proposal-store.js +465 -0
- package/dist/src/stores/lucid-action-proposal-store.js.map +1 -0
- package/dist/src/stores/lucid-schema.d.ts +8 -6
- package/dist/src/stores/lucid-schema.d.ts.map +1 -1
- package/dist/src/stores/lucid-schema.js +59 -6
- package/dist/src/stores/lucid-schema.js.map +1 -1
- package/dist/src/stores/lucid.d.ts +4 -2
- package/dist/src/stores/lucid.d.ts.map +1 -1
- package/dist/src/stores/lucid.js +43 -14
- package/dist/src/stores/lucid.js.map +1 -1
- package/dist/src/stores/sqlite-transactions.d.ts +8 -0
- package/dist/src/stores/sqlite-transactions.d.ts.map +1 -0
- package/dist/src/stores/sqlite-transactions.js +24 -0
- package/dist/src/stores/sqlite-transactions.js.map +1 -0
- package/dist/src/stream-events.d.ts +4 -0
- package/dist/src/stream-events.d.ts.map +1 -1
- package/dist/src/testing/in-memory-action-proposal-store.d.ts +32 -0
- package/dist/src/testing/in-memory-action-proposal-store.d.ts.map +1 -0
- package/dist/src/testing/in-memory-action-proposal-store.js +159 -0
- package/dist/src/testing/in-memory-action-proposal-store.js.map +1 -0
- package/dist/src/testing/in-memory-store.d.ts +9 -1
- package/dist/src/testing/in-memory-store.d.ts.map +1 -1
- package/dist/src/testing/in-memory-store.js +79 -1
- package/dist/src/testing/in-memory-store.js.map +1 -1
- package/dist/src/testing/index.d.ts +1 -0
- package/dist/src/testing/index.d.ts.map +1 -1
- package/dist/src/testing/index.js +1 -0
- package/dist/src/testing/index.js.map +1 -1
- package/dist/src/tool-discovery.d.ts.map +1 -1
- package/dist/src/tool-discovery.js +7 -0
- package/dist/src/tool-discovery.js.map +1 -1
- package/dist/src/tool-registry.d.ts +25 -1
- package/dist/src/tool-registry.d.ts.map +1 -1
- package/dist/src/tool-registry.js +73 -3
- package/dist/src/tool-registry.js.map +1 -1
- package/dist/src/tool-retry.d.ts +0 -11
- package/dist/src/tool-retry.d.ts.map +1 -1
- package/dist/src/tool-retry.js +5 -1
- package/dist/src/tool-retry.js.map +1 -1
- package/dist/src/tool-ui.d.ts +4 -1
- package/dist/src/tool-ui.d.ts.map +1 -1
- package/dist/src/tool-ui.js +32 -4
- package/dist/src/tool-ui.js.map +1 -1
- package/dist/src/types.d.ts +28 -2
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/ui-capabilities.d.ts +9 -0
- package/dist/src/ui-capabilities.d.ts.map +1 -0
- package/dist/src/ui-capabilities.js +25 -0
- package/dist/src/ui-capabilities.js.map +1 -0
- package/dist/stubs/database/migrations/add_action_proposal_discovery.stub +24 -0
- package/dist/stubs/database/migrations/add_action_proposal_runtime.stub +19 -0
- package/dist/stubs/database/migrations/create_agent_tables.stub +3 -3
- package/package.json +5 -5
package/dist/src/agent-loop.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { resolveActionProposalApproval } from './action-proposal-approval.js';
|
|
3
|
+
import { prepareActionProposal } from './action-proposal-preparation.js';
|
|
4
|
+
import { actionProposalReceipt } from './action-proposal-receipt.js';
|
|
1
5
|
import { danglingToolCallIds, settleDanglingToolCalls } from './dangling-tool-calls.js';
|
|
2
6
|
import { detachedDelivered, detachedStarted, } from './delegation.js';
|
|
3
7
|
import { publishAgentDelegated, publishAgentMessage, publishAgentRetrieved, publishAgentRunFinished, publishAgentRunStarted, publishAgentToolCall, publishAgentToolRetry, spannedAgent, } from './diagnostics.js';
|
|
4
8
|
import { ASK_TOOL_NAME, askInputSchema, askToolDefinition, DEFAULT_INTAKE_PREAMBLE, isHumanDecision, normalizeElicitationReply, settleElicitation, } from './elicitation.js';
|
|
5
9
|
import { buildMemoryBlock, offerMemories, REMEMBER_TOOL_NAME, rememberInputSchema, rememberToolDefinition, writeMemory, } from './memory.js';
|
|
6
|
-
import {
|
|
10
|
+
import { createNegotiatedUiCollector } from './negotiated-tool-ui.js';
|
|
11
|
+
import { personaFromTurn } from './personas.js';
|
|
7
12
|
import { createFrameBuffer, createIncrementalGate, gateTail, releaseGatedFrames, resolveGateLookback, resolveOutputGateMode, runInputProcessors, runOutputProcessors, } from './processors.js';
|
|
8
13
|
import { isReplayIntegrityError } from './replay-integrity.js';
|
|
9
14
|
import { buildSkillsBlock, loadSkill, offerSkills, SKILL_TOOL_NAME, skillInputSchema, skillToolDefinition, } from './skills.js';
|
|
@@ -12,9 +17,9 @@ import { withSelectedModel } from './spi/model-catalog.js';
|
|
|
12
17
|
import { estimateCost, resolveModelPrice, } from './spi/pricing-store.js';
|
|
13
18
|
import { OutputRejectedError, } from './spi/processors.js';
|
|
14
19
|
import { DEFAULT_STRUCTURED_OUTPUT_INSTRUCTION, repairInstruction, StructuredOutputError, validateStructured, } from './structured-output.js';
|
|
15
|
-
import { ToolForbiddenError, ToolInputInvalidError } from './tool-registry.js';
|
|
20
|
+
import { ToolForbiddenError, ToolInputInvalidError, ToolPreflightDeniedError, } from './tool-registry.js';
|
|
16
21
|
import { invokeWithTransientRetry } from './tool-retry.js';
|
|
17
|
-
import { createNoopEmitUi,
|
|
22
|
+
import { createNoopEmitUi, escapeUnsafeToolUiText, mergeUi, unwrapToolStepOutput, wrapToolStepOutput, } from './tool-ui.js';
|
|
18
23
|
import { observeTurnFrames, withTurnFrames } from './turn-frames.js';
|
|
19
24
|
/** Renders retrieved passages as a numbered, citable context block appended to the system prompt. */
|
|
20
25
|
function buildContextBlock(passages) {
|
|
@@ -93,6 +98,7 @@ function promptContext(input, persona) {
|
|
|
93
98
|
actor: input.actor,
|
|
94
99
|
...(persona !== undefined ? { persona: personaFromTurn(persona) } : {}),
|
|
95
100
|
...(input.pageContext !== undefined ? { pageContext: input.pageContext } : {}),
|
|
101
|
+
...(input.uiCapabilities !== undefined ? { uiCapabilities: input.uiCapabilities } : {}),
|
|
96
102
|
};
|
|
97
103
|
}
|
|
98
104
|
/**
|
|
@@ -313,6 +319,7 @@ function threadForTurn(recorded) {
|
|
|
313
319
|
const messages = settleDanglingToolCalls((recorded?.messages ?? []).map(toModelMessage));
|
|
314
320
|
return {
|
|
315
321
|
messages,
|
|
322
|
+
actionApprovalMode: recorded?.actionApprovalMode ?? 'blocking',
|
|
316
323
|
title: recorded?.title ?? null,
|
|
317
324
|
hasAssistantMessage: recorded?.hasAssistantMessage ?? messages.some((message) => message.role === 'assistant'),
|
|
318
325
|
};
|
|
@@ -622,6 +629,7 @@ function elicitationContext(deps, input, hooks) {
|
|
|
622
629
|
...(input.agentName !== undefined ? { agentName: input.agentName } : {}),
|
|
623
630
|
...personaContext(input),
|
|
624
631
|
...(input.pageContext !== undefined ? { pageContext: input.pageContext } : {}),
|
|
632
|
+
...(input.uiCapabilities !== undefined ? { uiCapabilities: input.uiCapabilities } : {}),
|
|
625
633
|
...(deps.host !== undefined ? { host: deps.host } : {}),
|
|
626
634
|
};
|
|
627
635
|
}
|
|
@@ -653,6 +661,20 @@ const MESSAGE_TOOL_RESULTS_PATCH = 'agent:message-tool-results';
|
|
|
653
661
|
async function claimToolCall(turn, call) {
|
|
654
662
|
const { deps, input, hooks, messageId, writer } = turn;
|
|
655
663
|
const persona = personaOf(input);
|
|
664
|
+
const ctx = {
|
|
665
|
+
actor: input.actor,
|
|
666
|
+
threadId: input.threadId,
|
|
667
|
+
runId: hooks.runId,
|
|
668
|
+
requestId: hooks.runId,
|
|
669
|
+
// Replaced by the call's own collector inside its `tool:` step (see invokeClaimedTool); a
|
|
670
|
+
// kind that never reaches a handler keeps this no-op.
|
|
671
|
+
emitUi: createNoopEmitUi(call.id),
|
|
672
|
+
...(input.agentName !== undefined ? { agentName: input.agentName } : {}),
|
|
673
|
+
...(persona !== undefined ? { persona } : {}),
|
|
674
|
+
...(input.pageContext !== undefined ? { pageContext: input.pageContext } : {}),
|
|
675
|
+
...(input.uiCapabilities !== undefined ? { uiCapabilities: input.uiCapabilities } : {}),
|
|
676
|
+
...(deps.host !== undefined ? { host: deps.host } : {}),
|
|
677
|
+
};
|
|
656
678
|
const persisted = (await hooks.step(`persist:toolcall:${call.id}`, async () => {
|
|
657
679
|
// The kind the llm checkpoint stamped WINS: it came from the process that offered the tool,
|
|
658
680
|
// whereas this one is only the process claiming the call — and the two disagree exactly when
|
|
@@ -682,15 +704,138 @@ async function claimToolCall(turn, call) {
|
|
|
682
704
|
// set can both change while a run is parked, and the branch below (park or run) has to be the
|
|
683
705
|
// one the journal recorded. A run claimed before this existed reads `approval` back as
|
|
684
706
|
// undefined and waits on the requester, exactly as it did.
|
|
685
|
-
|
|
686
|
-
|
|
707
|
+
let actionApproval = call.actionApproval;
|
|
708
|
+
if (kind === 'action' &&
|
|
709
|
+
turn.actionApprovalMode === 'independent' &&
|
|
710
|
+
actionApproval === undefined)
|
|
711
|
+
actionApproval = await resolveActionProposalApproval({
|
|
712
|
+
actor: input.actor,
|
|
713
|
+
thread: {
|
|
714
|
+
threadId: input.threadId,
|
|
715
|
+
runId: hooks.runId,
|
|
716
|
+
...(input.agentName !== undefined ? { agentName: input.agentName } : {}),
|
|
717
|
+
},
|
|
718
|
+
tool: {
|
|
719
|
+
name: call.name,
|
|
720
|
+
kind: 'action',
|
|
721
|
+
...(deps.registry.spec(call.name) !== undefined
|
|
722
|
+
? { spec: deps.registry.spec(call.name) }
|
|
723
|
+
: {}),
|
|
724
|
+
},
|
|
725
|
+
store: deps.store,
|
|
726
|
+
...(deps.approvalPolicy !== undefined ? { policy: deps.approvalPolicy } : {}),
|
|
727
|
+
});
|
|
728
|
+
let preflight = call.preflight;
|
|
729
|
+
let prepared = call.prepared;
|
|
730
|
+
if (kind === 'action' && preflight === undefined && deps.registry.has(call.name)) {
|
|
731
|
+
try {
|
|
732
|
+
const allowedTools = intersectAllow(persona?.allowedTools, deps.toolAllowList);
|
|
733
|
+
if (turn.actionApprovalMode === 'independent' && actionApproval?.mode === 'ask') {
|
|
734
|
+
prepared = await prepareActionProposal(deps.registry, call.name, call.input, { ...ctx, toolCallId: call.id, idempotencyKey: `${hooks.runId}:${call.id}` }, deps.rolesPolicy, allowedTools === undefined ? {} : { allowedTools });
|
|
735
|
+
preflight = prepared.preflight;
|
|
736
|
+
}
|
|
737
|
+
else
|
|
738
|
+
preflight = await deps.registry.prepare(call.name, call.input, {
|
|
739
|
+
...ctx,
|
|
740
|
+
toolCallId: call.id,
|
|
741
|
+
idempotencyKey: `${hooks.runId}:${call.id}`,
|
|
742
|
+
}, deps.rolesPolicy, allowedTools === undefined ? {} : { allowedTools });
|
|
743
|
+
}
|
|
744
|
+
catch (error) {
|
|
745
|
+
if (isReplayIntegrityError(error) || hooks.isControlFlowError?.(error) === true)
|
|
746
|
+
throw error;
|
|
747
|
+
preflight = {
|
|
748
|
+
status: 'failed',
|
|
749
|
+
error: error instanceof Error ? error.message : String(error),
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
const early = preflight !== undefined && preflight.status !== 'ready';
|
|
754
|
+
let confirmation = preflight?.status === 'ready' ? preflight.confirmation : undefined;
|
|
755
|
+
const approval = kind === 'action' && !early
|
|
756
|
+
? (actionApproval ?? (await claimApproval(turn, call)))
|
|
757
|
+
: undefined;
|
|
758
|
+
let proposalId;
|
|
759
|
+
if (turn.actionApprovalMode === 'independent' && approval?.mode === 'ask') {
|
|
760
|
+
if (!prepared)
|
|
761
|
+
throw new Error('Independent action lacks trusted prepared input');
|
|
762
|
+
const store = deps.store;
|
|
763
|
+
if (typeof store.createActionProposal !== 'function')
|
|
764
|
+
throw new Error('Independent proposal store unavailable');
|
|
765
|
+
proposalId = `proposal-${createHash('sha256')
|
|
766
|
+
.update(JSON.stringify([hooks.runId, call.id]))
|
|
767
|
+
.digest('hex')}`;
|
|
768
|
+
const display = confirmation ??
|
|
769
|
+
deps.registry.spec(call.name)?.presentation?.confirm ?? {
|
|
770
|
+
title: `Allow ${call.name}?`,
|
|
771
|
+
verb: 'Approve',
|
|
772
|
+
};
|
|
773
|
+
const fill = (value) => value.replace(/\{([^}]+)\}/g, (_match, path) => {
|
|
774
|
+
let current = prepared.input;
|
|
775
|
+
for (const part of path.split('.'))
|
|
776
|
+
current =
|
|
777
|
+
current !== null && typeof current === 'object'
|
|
778
|
+
? current[part]
|
|
779
|
+
: undefined;
|
|
780
|
+
return current === undefined ? '' : String(current);
|
|
781
|
+
});
|
|
782
|
+
const create = prepared.replacementKey === undefined
|
|
783
|
+
? store.createActionProposal.bind(store)
|
|
784
|
+
: store.createReplacingActionProposal.bind(store);
|
|
785
|
+
const created = await create({
|
|
786
|
+
...(prepared.replacementKey !== undefined
|
|
787
|
+
? { replacementKey: prepared.replacementKey }
|
|
788
|
+
: {}),
|
|
789
|
+
id: proposalId,
|
|
790
|
+
tenantRef: input.actor.tenantRef ?? null,
|
|
791
|
+
actorRef: input.actor.id,
|
|
792
|
+
threadId: input.threadId,
|
|
793
|
+
originRunId: hooks.runId,
|
|
794
|
+
originMessageId: messageId,
|
|
795
|
+
originToolCallId: call.id,
|
|
796
|
+
toolName: call.name,
|
|
797
|
+
input: prepared.input,
|
|
798
|
+
preparationInput: prepared.preparationInput,
|
|
799
|
+
executionContext: {
|
|
800
|
+
requestId: hooks.runId,
|
|
801
|
+
...(input.agentName !== undefined ? { agentName: input.agentName } : {}),
|
|
802
|
+
...(persona !== undefined ? { persona: persona.id } : {}),
|
|
803
|
+
...(input.pageContext !== undefined ? { pageContext: input.pageContext } : {}),
|
|
804
|
+
...(input.uiCapabilities !== undefined ? { uiCapabilities: input.uiCapabilities } : {}),
|
|
805
|
+
},
|
|
806
|
+
confirmation: confirmation ?? {
|
|
807
|
+
title: fill(display.title),
|
|
808
|
+
verb: fill(display.verb),
|
|
809
|
+
...(display.detail !== undefined ? { detail: fill(display.detail) } : {}),
|
|
810
|
+
},
|
|
811
|
+
approver: approval.approver,
|
|
812
|
+
expiresAt: approval.expiresAt === undefined ? null : Date.parse(approval.expiresAt),
|
|
813
|
+
idempotencyKey: `${hooks.runId}:${call.id}`,
|
|
814
|
+
});
|
|
815
|
+
if (created.status === 'conflict')
|
|
816
|
+
throw new Error('Action proposal replay payload conflict');
|
|
817
|
+
confirmation = created.proposal?.confirmation ?? confirmation;
|
|
818
|
+
}
|
|
819
|
+
const parks = kind === 'ask' || (approval?.mode === 'ask' && proposalId === undefined);
|
|
687
820
|
await deps.store.recordToolCall({
|
|
688
821
|
toolCallId: call.id,
|
|
689
822
|
messageId,
|
|
690
823
|
toolName: call.name,
|
|
691
824
|
toolType: awaitsHuman ? 'action' : 'read',
|
|
692
825
|
input: call.input,
|
|
693
|
-
|
|
826
|
+
...(proposalId !== undefined ? { proposalId } : {}),
|
|
827
|
+
status: proposalId !== undefined
|
|
828
|
+
? 'proposed'
|
|
829
|
+
: early
|
|
830
|
+
? preflight?.status === 'denied'
|
|
831
|
+
? 'rejected'
|
|
832
|
+
: preflight?.status === 'failed'
|
|
833
|
+
? 'failed'
|
|
834
|
+
: 'executed'
|
|
835
|
+
: parks
|
|
836
|
+
? 'pending_approval'
|
|
837
|
+
: 'auto_executed',
|
|
838
|
+
...(confirmation !== undefined ? { confirmation } : {}),
|
|
694
839
|
runId: hooks.runId,
|
|
695
840
|
...(approval !== undefined && approval.mode !== 'auto'
|
|
696
841
|
? { approver: approval.approver }
|
|
@@ -699,6 +844,19 @@ async function claimToolCall(turn, call) {
|
|
|
699
844
|
? { expiresAt: approval.expiresAt }
|
|
700
845
|
: {}),
|
|
701
846
|
});
|
|
847
|
+
if (early) {
|
|
848
|
+
await deps.store.updateToolCall({
|
|
849
|
+
toolCallId: call.id,
|
|
850
|
+
status: preflight?.status === 'denied'
|
|
851
|
+
? 'rejected'
|
|
852
|
+
: preflight?.status === 'failed'
|
|
853
|
+
? 'failed'
|
|
854
|
+
: 'executed',
|
|
855
|
+
...(preflight?.status === 'completed' ? { output: preflight.output } : {}),
|
|
856
|
+
...(preflight?.status === 'denied' ? { error: preflight.reason } : {}),
|
|
857
|
+
...(preflight?.status === 'failed' ? { error: preflight.error } : {}),
|
|
858
|
+
});
|
|
859
|
+
}
|
|
702
860
|
// Written from INSIDE this checkpoint, so the frame is streamed once and a replay — which
|
|
703
861
|
// returns the memoized result without re-running the body — never re-posts a form for a
|
|
704
862
|
// decision already made. That is also why it spends no position of its own.
|
|
@@ -708,11 +866,15 @@ async function claimToolCall(turn, call) {
|
|
|
708
866
|
if (approval?.mode === 'ask') {
|
|
709
867
|
await writer.write({
|
|
710
868
|
t: 'approval',
|
|
869
|
+
...(proposalId !== undefined
|
|
870
|
+
? { target: { kind: 'proposal', proposalId } }
|
|
871
|
+
: {}),
|
|
711
872
|
runId: hooks.runId,
|
|
712
873
|
id: call.id,
|
|
713
874
|
toolName: call.name,
|
|
714
875
|
input: call.input,
|
|
715
876
|
approver: approval.approver,
|
|
877
|
+
...(confirmation !== undefined ? { confirmation } : {}),
|
|
716
878
|
...(approval.expiresAt !== undefined ? { expiresAt: approval.expiresAt } : {}),
|
|
717
879
|
});
|
|
718
880
|
}
|
|
@@ -721,6 +883,8 @@ async function claimToolCall(turn, call) {
|
|
|
721
883
|
const terminal = deps.registry.spec(call.name)?.terminal === true;
|
|
722
884
|
return {
|
|
723
885
|
kind,
|
|
886
|
+
...(proposalId !== undefined ? { proposalId } : {}),
|
|
887
|
+
...(preflight !== undefined ? { preflight } : {}),
|
|
724
888
|
...(approval !== undefined ? { approval } : {}),
|
|
725
889
|
...(terminal ? { terminal: true } : {}),
|
|
726
890
|
};
|
|
@@ -732,24 +896,14 @@ async function claimToolCall(turn, call) {
|
|
|
732
896
|
return {
|
|
733
897
|
call,
|
|
734
898
|
toolType,
|
|
899
|
+
...(persisted?.preflight !== undefined ? { preflight: persisted.preflight } : {}),
|
|
735
900
|
...(persisted?.terminal === true ? { terminal: true } : {}),
|
|
901
|
+
...(persisted?.proposalId !== undefined ? { proposalId: persisted.proposalId } : {}),
|
|
736
902
|
...(persisted?.delegation !== undefined ? { delegation: persisted.delegation } : {}),
|
|
737
903
|
...(toolType === 'action' && persisted?.approval !== undefined
|
|
738
904
|
? { approval: persisted.approval }
|
|
739
905
|
: {}),
|
|
740
|
-
ctx
|
|
741
|
-
actor: input.actor,
|
|
742
|
-
threadId: input.threadId,
|
|
743
|
-
runId: hooks.runId,
|
|
744
|
-
requestId: hooks.runId,
|
|
745
|
-
// Replaced by the call's own collector inside its `tool:` step (see invokeClaimedTool); a
|
|
746
|
-
// kind that never reaches a handler keeps this no-op.
|
|
747
|
-
emitUi: createNoopEmitUi(call.id),
|
|
748
|
-
...(input.agentName !== undefined ? { agentName: input.agentName } : {}),
|
|
749
|
-
...(persona !== undefined ? { persona } : {}),
|
|
750
|
-
...(input.pageContext !== undefined ? { pageContext: input.pageContext } : {}),
|
|
751
|
-
...(deps.host !== undefined ? { host: deps.host } : {}),
|
|
752
|
-
},
|
|
906
|
+
ctx,
|
|
753
907
|
};
|
|
754
908
|
}
|
|
755
909
|
/**
|
|
@@ -814,17 +968,19 @@ function spanToolType(kind) {
|
|
|
814
968
|
async function invokeClaimedTool(turn, claimed) {
|
|
815
969
|
const { deps, hooks } = turn;
|
|
816
970
|
const { call, ctx, toolType } = claimed;
|
|
817
|
-
//
|
|
818
|
-
|
|
819
|
-
const
|
|
820
|
-
const invokeOptions = personaAllow === undefined
|
|
821
|
-
? {}
|
|
822
|
-
: { allowedTools: intersectAllowLists(deps.toolAllowList, personaAllow) ?? [] };
|
|
971
|
+
// Execution retains every narrowing the offer and preparation had, including an agent-only list.
|
|
972
|
+
const allowedTools = intersectAllow(personaOf(turn.input)?.allowedTools, deps.toolAllowList);
|
|
973
|
+
const invokeOptions = allowedTools === undefined ? {} : { allowedTools };
|
|
823
974
|
try {
|
|
824
975
|
const raw = await hooks.step(`tool:${call.id}`, async () => {
|
|
825
976
|
// Pushed components stream as they happen and ride this step's RESULT into the journal (see
|
|
826
977
|
// `wrapToolStepOutput`), so a replay neither re-streams nor re-persists them.
|
|
827
|
-
const ui =
|
|
978
|
+
const ui = createNegotiatedUiCollector(call.id, {
|
|
979
|
+
actor: ctx.actor,
|
|
980
|
+
threadId: ctx.threadId,
|
|
981
|
+
...(ctx.agentName !== undefined ? { agentName: ctx.agentName } : {}),
|
|
982
|
+
...(ctx.uiCapabilities !== undefined ? { uiCapabilities: ctx.uiCapabilities } : {}),
|
|
983
|
+
}, deps.resolveUiCatalog, turn.writer);
|
|
828
984
|
const toolCtx = {
|
|
829
985
|
...ctx,
|
|
830
986
|
// The same for every execution of THIS call (a replay after a crash, a transient retry).
|
|
@@ -838,31 +994,50 @@ async function invokeClaimedTool(turn, claimed) {
|
|
|
838
994
|
: { value: data });
|
|
839
995
|
},
|
|
840
996
|
};
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
997
|
+
let output;
|
|
998
|
+
try {
|
|
999
|
+
output = await spannedAgent('tool.execution', hooks.runId, {
|
|
1000
|
+
runId: hooks.runId,
|
|
1001
|
+
toolCallId: call.id,
|
|
1002
|
+
toolName: call.name,
|
|
1003
|
+
toolType: spanToolType(toolType),
|
|
1004
|
+
}, () => invokeWithTransientRetry(() => {
|
|
1005
|
+
ui.restart();
|
|
1006
|
+
return deps.registry.invoke(call.name, call.input, toolCtx, deps.rolesPolicy, invokeOptions);
|
|
1007
|
+
}, deps.toolTransientRetry ?? {}, {
|
|
1008
|
+
...(hooks.isControlFlowError !== undefined
|
|
1009
|
+
? { isControlFlowError: hooks.isControlFlowError }
|
|
1010
|
+
: {}),
|
|
1011
|
+
onRetry: (attempt, retryError) => {
|
|
1012
|
+
publishAgentToolRetry({
|
|
1013
|
+
runId: hooks.runId,
|
|
1014
|
+
toolName: call.name,
|
|
1015
|
+
toolCallId: call.id,
|
|
1016
|
+
attempt,
|
|
1017
|
+
message: retryError instanceof Error ? retryError.message : String(retryError),
|
|
1018
|
+
});
|
|
1019
|
+
},
|
|
1020
|
+
}), () => ({}));
|
|
1021
|
+
}
|
|
1022
|
+
catch (error) {
|
|
1023
|
+
// A domain refusal is a settled result, so a durable replay must not re-run the check.
|
|
1024
|
+
if (!(error instanceof ToolPreflightDeniedError))
|
|
1025
|
+
throw error;
|
|
1026
|
+
return { '@@adonis-agent/tool-preflight-denied': 1, reason: error.reason };
|
|
1027
|
+
}
|
|
1028
|
+
return wrapToolStepOutput(output, ui.components(), ui.text());
|
|
864
1029
|
});
|
|
865
|
-
|
|
1030
|
+
if (typeof raw === 'object' &&
|
|
1031
|
+
raw !== null &&
|
|
1032
|
+
'@@adonis-agent/tool-preflight-denied' in raw &&
|
|
1033
|
+
raw['@@adonis-agent/tool-preflight-denied'] === 1 &&
|
|
1034
|
+
'reason' in raw &&
|
|
1035
|
+
typeof raw.reason === 'string') {
|
|
1036
|
+
return { status: 'failed', error: raw.reason, denied: true };
|
|
1037
|
+
}
|
|
1038
|
+
const { output, ui, text } = unwrapToolStepOutput(raw);
|
|
1039
|
+
if (text)
|
|
1040
|
+
turn.toolText.set(call.id, text);
|
|
866
1041
|
if (ui.length > 0) {
|
|
867
1042
|
turn.toolUi.set(call.id, ui);
|
|
868
1043
|
}
|
|
@@ -875,7 +1050,11 @@ async function invokeClaimedTool(turn, claimed) {
|
|
|
875
1050
|
if (isReplayIntegrityError(error) || hooks.isControlFlowError?.(error) === true) {
|
|
876
1051
|
throw error;
|
|
877
1052
|
}
|
|
878
|
-
return {
|
|
1053
|
+
return {
|
|
1054
|
+
status: 'failed',
|
|
1055
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1056
|
+
...(error instanceof ToolPreflightDeniedError ? { denied: true } : {}),
|
|
1057
|
+
};
|
|
879
1058
|
}
|
|
880
1059
|
}
|
|
881
1060
|
/** Persist one settled invocation and shape the result the model is fed. */
|
|
@@ -922,7 +1101,13 @@ async function recordToolOutcome(turn, claimed, outcome, deciderRef = turn.input
|
|
|
922
1101
|
await settle();
|
|
923
1102
|
});
|
|
924
1103
|
publishAgentToolCall({ runId: hooks.runId, toolName: call.name, toolType, status: 'failed' });
|
|
925
|
-
return {
|
|
1104
|
+
return {
|
|
1105
|
+
id: call.id,
|
|
1106
|
+
name: call.name,
|
|
1107
|
+
output: outcome.denied ? { reason: outcome.error } : null,
|
|
1108
|
+
error: outcome.error,
|
|
1109
|
+
...(outcome.denied ? { denied: true } : {}),
|
|
1110
|
+
};
|
|
926
1111
|
}
|
|
927
1112
|
await hooks.step(`persist:toolexec:${call.id}`, async () => {
|
|
928
1113
|
await deps.store.updateToolCall({
|
|
@@ -986,14 +1171,95 @@ async function delegateToolCall(turn, claimed) {
|
|
|
986
1171
|
* what puts the kind in the journal — so the branch a call takes is a fact about the turn rather
|
|
987
1172
|
* than about whichever process happened to replay it (see {@link claimToolCall}).
|
|
988
1173
|
*
|
|
989
|
-
*
|
|
990
|
-
*
|
|
1174
|
+
* Provider-supplied preparation is discarded. The trusted result rides this same model checkpoint
|
|
1175
|
+
* so a process that first claims the call without its handler still follows the offering process's verdict.
|
|
991
1176
|
*/
|
|
992
|
-
function stampToolKinds(result, deps) {
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1177
|
+
async function stampToolKinds(result, deps, input, hooks) {
|
|
1178
|
+
const persona = personaOf(input);
|
|
1179
|
+
const allowedTools = intersectAllow(persona?.allowedTools, deps.toolAllowList);
|
|
1180
|
+
const toolCalls = [];
|
|
1181
|
+
for (const incoming of result.toolCalls) {
|
|
1182
|
+
// The provider cannot supply trusted preparation. Resolve it only where the tool was offered.
|
|
1183
|
+
const { preflight: _untrusted, prepared: _untrustedPrepared, actionApproval: _untrustedApproval, ...call } = incoming;
|
|
1184
|
+
const kind = declaredKind(deps, call.name);
|
|
1185
|
+
let preflight;
|
|
1186
|
+
let prepared;
|
|
1187
|
+
let actionApproval;
|
|
1188
|
+
if (kind === 'action') {
|
|
1189
|
+
try {
|
|
1190
|
+
if (deps.actionApprovalMode === 'independent')
|
|
1191
|
+
actionApproval = await resolveActionProposalApproval({
|
|
1192
|
+
actor: input.actor,
|
|
1193
|
+
thread: {
|
|
1194
|
+
threadId: input.threadId,
|
|
1195
|
+
runId: hooks.runId,
|
|
1196
|
+
...(input.agentName !== undefined ? { agentName: input.agentName } : {}),
|
|
1197
|
+
},
|
|
1198
|
+
tool: {
|
|
1199
|
+
name: call.name,
|
|
1200
|
+
kind: 'action',
|
|
1201
|
+
...(deps.registry.spec(call.name) !== undefined
|
|
1202
|
+
? { spec: deps.registry.spec(call.name) }
|
|
1203
|
+
: {}),
|
|
1204
|
+
},
|
|
1205
|
+
store: deps.store,
|
|
1206
|
+
...(deps.approvalPolicy !== undefined ? { policy: deps.approvalPolicy } : {}),
|
|
1207
|
+
});
|
|
1208
|
+
if (deps.actionApprovalMode === 'independent' && actionApproval?.mode === 'ask') {
|
|
1209
|
+
prepared = await prepareActionProposal(deps.registry, call.name, call.input, {
|
|
1210
|
+
actor: input.actor,
|
|
1211
|
+
threadId: input.threadId,
|
|
1212
|
+
runId: hooks.runId,
|
|
1213
|
+
requestId: hooks.runId,
|
|
1214
|
+
toolCallId: call.id,
|
|
1215
|
+
idempotencyKey: `${hooks.runId}:${call.id}`,
|
|
1216
|
+
emitUi: createNoopEmitUi(call.id),
|
|
1217
|
+
...(input.agentName !== undefined ? { agentName: input.agentName } : {}),
|
|
1218
|
+
...(persona !== undefined ? { persona } : {}),
|
|
1219
|
+
...(input.pageContext !== undefined ? { pageContext: input.pageContext } : {}),
|
|
1220
|
+
...(input.uiCapabilities !== undefined
|
|
1221
|
+
? { uiCapabilities: input.uiCapabilities }
|
|
1222
|
+
: {}),
|
|
1223
|
+
...(deps.host !== undefined ? { host: deps.host } : {}),
|
|
1224
|
+
}, deps.rolesPolicy, allowedTools === undefined ? {} : { allowedTools });
|
|
1225
|
+
preflight = prepared.preflight;
|
|
1226
|
+
}
|
|
1227
|
+
else
|
|
1228
|
+
preflight = await deps.registry.prepare(call.name, call.input, {
|
|
1229
|
+
actor: input.actor,
|
|
1230
|
+
threadId: input.threadId,
|
|
1231
|
+
runId: hooks.runId,
|
|
1232
|
+
requestId: hooks.runId,
|
|
1233
|
+
toolCallId: call.id,
|
|
1234
|
+
idempotencyKey: `${hooks.runId}:${call.id}`,
|
|
1235
|
+
emitUi: createNoopEmitUi(call.id),
|
|
1236
|
+
...(input.agentName !== undefined ? { agentName: input.agentName } : {}),
|
|
1237
|
+
...(persona !== undefined ? { persona } : {}),
|
|
1238
|
+
...(input.pageContext !== undefined ? { pageContext: input.pageContext } : {}),
|
|
1239
|
+
...(input.uiCapabilities !== undefined
|
|
1240
|
+
? { uiCapabilities: input.uiCapabilities }
|
|
1241
|
+
: {}),
|
|
1242
|
+
...(deps.host !== undefined ? { host: deps.host } : {}),
|
|
1243
|
+
}, deps.rolesPolicy, allowedTools === undefined ? {} : { allowedTools });
|
|
1244
|
+
}
|
|
1245
|
+
catch (error) {
|
|
1246
|
+
if (isReplayIntegrityError(error) || hooks.isControlFlowError?.(error) === true)
|
|
1247
|
+
throw error;
|
|
1248
|
+
preflight = {
|
|
1249
|
+
status: 'failed',
|
|
1250
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
toolCalls.push({
|
|
1255
|
+
...call,
|
|
1256
|
+
...(actionApproval !== undefined ? { actionApproval } : {}),
|
|
1257
|
+
kind,
|
|
1258
|
+
...(prepared !== undefined ? { prepared } : {}),
|
|
1259
|
+
...(preflight !== undefined ? { preflight } : {}),
|
|
1260
|
+
});
|
|
1261
|
+
}
|
|
1262
|
+
return { ...result, toolCalls };
|
|
997
1263
|
}
|
|
998
1264
|
/**
|
|
999
1265
|
* A call's declared kind, resolved from this process's own configuration and registry.
|
|
@@ -1077,6 +1343,7 @@ function skillContext(input) {
|
|
|
1077
1343
|
threadId: input.threadId,
|
|
1078
1344
|
...(input.agentName !== undefined ? { agentName: input.agentName } : {}),
|
|
1079
1345
|
...(input.pageContext !== undefined ? { pageContext: input.pageContext } : {}),
|
|
1346
|
+
...(input.uiCapabilities !== undefined ? { uiCapabilities: input.uiCapabilities } : {}),
|
|
1080
1347
|
};
|
|
1081
1348
|
}
|
|
1082
1349
|
/**
|
|
@@ -1200,6 +1467,26 @@ function invokeClaimed(turn, claimed) {
|
|
|
1200
1467
|
async function runClaimedToolCall(turn, claimed) {
|
|
1201
1468
|
const { deps, hooks } = turn;
|
|
1202
1469
|
const { call, toolType, ctx } = claimed;
|
|
1470
|
+
if (claimed.proposalId !== undefined) {
|
|
1471
|
+
turn.halt.independentProposal = true;
|
|
1472
|
+
return { id: call.id, name: call.name, output: actionProposalReceipt(claimed.proposalId) };
|
|
1473
|
+
}
|
|
1474
|
+
if (claimed.preflight !== undefined && claimed.preflight.status !== 'ready') {
|
|
1475
|
+
const result = claimed.preflight;
|
|
1476
|
+
if (result.status === 'completed') {
|
|
1477
|
+
if (claimed.terminal === true)
|
|
1478
|
+
turn.halt.terminal = true;
|
|
1479
|
+
return { id: call.id, name: call.name, output: result.output };
|
|
1480
|
+
}
|
|
1481
|
+
const reason = result.status === 'denied' ? result.reason : result.error;
|
|
1482
|
+
return {
|
|
1483
|
+
id: call.id,
|
|
1484
|
+
name: call.name,
|
|
1485
|
+
output: result.status === 'denied' ? { reason } : null,
|
|
1486
|
+
error: reason,
|
|
1487
|
+
...(result.status === 'denied' ? { denied: true } : {}),
|
|
1488
|
+
};
|
|
1489
|
+
}
|
|
1203
1490
|
if (toolType === 'agent') {
|
|
1204
1491
|
return delegateToolCall(turn, claimed);
|
|
1205
1492
|
}
|
|
@@ -1704,7 +1991,9 @@ export async function runAgentLoop(boundDeps, requested, hooks) {
|
|
|
1704
1991
|
...(input.attachments !== undefined ? { attachments: input.attachments } : {}),
|
|
1705
1992
|
}));
|
|
1706
1993
|
}
|
|
1707
|
-
const thread = threadForTurn(await hooks.step('load:thread', () => readThreadForTurn(deps, input.threadId, turnMessageLimit(deps.historyWindow)))
|
|
1994
|
+
const thread = threadForTurn(await hooks.step('load:thread', () => readThreadForTurn(deps, input.threadId, turnMessageLimit(deps.historyWindow)).then((loaded) => loaded === null
|
|
1995
|
+
? { messages: [], actionApprovalMode: deps.actionApprovalMode ?? 'blocking' }
|
|
1996
|
+
: { ...loaded, actionApprovalMode: deps.actionApprovalMode ?? 'blocking' })));
|
|
1708
1997
|
// With no window configured this whole block is skipped, so an existing deployment sends the
|
|
1709
1998
|
// messages `load:thread` returned and records byte-identical checkpoints.
|
|
1710
1999
|
let modelMessages = thread.messages;
|
|
@@ -1884,6 +2173,7 @@ export async function runAgentLoop(boundDeps, requested, hooks) {
|
|
|
1884
2173
|
const tools = withBuiltInTools({
|
|
1885
2174
|
tools: await deps.registry.definitionsFor(input.actor, deps.rolesPolicy, intersectAllow(persona?.allowedTools, deps.toolAllowList), {
|
|
1886
2175
|
threadId: input.threadId,
|
|
2176
|
+
...(input.uiCapabilities !== undefined ? { uiCapabilities: input.uiCapabilities } : {}),
|
|
1887
2177
|
...(input.agentName !== undefined ? { agentName: input.agentName } : {}),
|
|
1888
2178
|
}),
|
|
1889
2179
|
ask: deps.ask,
|
|
@@ -1945,7 +2235,7 @@ export async function runAgentLoop(boundDeps, requested, hooks) {
|
|
|
1945
2235
|
outputTokens: result.usage.outputTokens,
|
|
1946
2236
|
textLength: result.text.length,
|
|
1947
2237
|
toolCalls: result.toolCalls.length,
|
|
1948
|
-
})), deps));
|
|
2238
|
+
})), { ...deps, actionApprovalMode: thread.actionApprovalMode ?? 'blocking' }, input, hooks));
|
|
1949
2239
|
// The gate runs before ANYTHING downstream: before the held frames reach the subscriber, before
|
|
1950
2240
|
// the assistant message is persisted, before this text becomes the next step's context.
|
|
1951
2241
|
let rejection;
|
|
@@ -2171,7 +2461,9 @@ export async function runAgentLoop(boundDeps, requested, hooks) {
|
|
|
2171
2461
|
messageId: assistant.id,
|
|
2172
2462
|
writer,
|
|
2173
2463
|
toolUi: new Map(),
|
|
2464
|
+
toolText: new Map(),
|
|
2174
2465
|
halt: { terminal: false },
|
|
2466
|
+
actionApprovalMode: thread.actionApprovalMode ?? 'blocking',
|
|
2175
2467
|
...(skillOffer !== undefined ? { skills: skillOffer } : {}),
|
|
2176
2468
|
...(memoryDigest !== undefined ? { memory: memoryDigest } : {}),
|
|
2177
2469
|
};
|
|
@@ -2231,6 +2523,16 @@ export async function runAgentLoop(boundDeps, requested, hooks) {
|
|
|
2231
2523
|
await deps.store.setMessageToolResults(assistant.id, [...results, ...syntheticResults]);
|
|
2232
2524
|
// What the tools pushed joins what the model turn streamed, on the same message, in call
|
|
2233
2525
|
// order. Every list here was read off a checkpoint above, so a replay writes the same value.
|
|
2526
|
+
const fallbackText = escapeUnsafeToolUiText(turn.toolCalls
|
|
2527
|
+
.map((call) => turnCalls.toolText.get(call.id))
|
|
2528
|
+
.filter(Boolean)
|
|
2529
|
+
.join('\n'));
|
|
2530
|
+
if (fallbackText)
|
|
2531
|
+
await deps.store.appendMessage({
|
|
2532
|
+
threadId: input.threadId,
|
|
2533
|
+
role: 'assistant',
|
|
2534
|
+
content: fallbackText,
|
|
2535
|
+
});
|
|
2234
2536
|
const toolUi = turn.toolCalls.flatMap((call) => turnCalls.toolUi.get(call.id) ?? []);
|
|
2235
2537
|
if (toolUi.length > 0) {
|
|
2236
2538
|
await deps.store.setMessageUi?.(assistant.id, mergeUi(turn.ui, toolUi));
|
|
@@ -2244,9 +2546,17 @@ export async function runAgentLoop(boundDeps, requested, hooks) {
|
|
|
2244
2546
|
});
|
|
2245
2547
|
}
|
|
2246
2548
|
modelMessages.push({ role: 'user', content: '', toolResults: results });
|
|
2549
|
+
const toolText = escapeUnsafeToolUiText(turn.toolCalls
|
|
2550
|
+
.map((call) => turnCalls.toolText.get(call.id))
|
|
2551
|
+
.filter(Boolean)
|
|
2552
|
+
.join('\n'));
|
|
2553
|
+
if (toolText) {
|
|
2554
|
+
modelMessages.push({ role: 'assistant', content: toolText });
|
|
2555
|
+
lastText += `\n${toolText}`;
|
|
2556
|
+
}
|
|
2247
2557
|
// A `terminal` tool succeeded: its effect is the answer, so no model call narrates it. Read off
|
|
2248
2558
|
// the calls' `persist:toolcall` checkpoints and their outcomes, so a replay ends here too.
|
|
2249
|
-
if (turnCalls.halt.terminal) {
|
|
2559
|
+
if (turnCalls.halt.terminal || turnCalls.halt.independentProposal) {
|
|
2250
2560
|
break;
|
|
2251
2561
|
}
|
|
2252
2562
|
}
|