Merge remote-tracking branch 'origin/master' into xtr/react-loop-simplification
# Conflicts: # .agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.i18n.yaml # .agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.md # .agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.zh.md # .agents/notes/implemented/feature/2026-07-21-tui-skill-slash-command.i18n.yaml # .agents/notes/implemented/feature/2026-07-21-tui-skill-slash-command.md # .agents/notes/implemented/feature/2026-07-21-tui-skill-slash-command.zh.md # .agents/notes/implemented/simplification/2026-07-17-one-send-one-turn.i18n.yaml # .agents/notes/implemented/simplification/2026-07-17-one-send-one-turn.md # .agents/notes/implemented/simplification/2026-07-17-one-send-one-turn.zh.md # docs/architecture.i18n.yaml # docs/cordis-catalog/events.md # docs/cordis-catalog/services.md # docs/core-data-structures/core.i18n.yaml # docs/core-data-structures/core.md # docs/core-data-structures/core.zh.md # docs/defensive-patterns.i18n.yaml # packages/client/runtime/src/client/sessions/session.ts # packages/client/runtime/tests/queue-store.spec.ts # packages/context/time-context/tests/time-context.spec.ts # packages/context/workspace-context/tests/workspace-context.spec.ts # packages/cordis/tool-cordis/src/api-catalog.ts # packages/core/agent-loop/README.i18n.yaml # packages/core/agent-loop/README.md # packages/core/agent-loop/README.zh.md # packages/core/agent-loop/src/agent.ts # packages/core/agent/README.i18n.yaml # packages/core/agent/README.md # packages/core/agent/README.zh.md # packages/core/agent/src/types.ts # packages/core/agent/tests/agent.spec.ts # packages/core/scope/src/scoped-events.generated.ts # packages/goal/command-goal/tests/command-goal.spec.ts # packages/goal/goal-session/src/index.ts # packages/goal/goal-session/tests/goal-session.spec.ts # packages/goal/goal/tests/goal.spec.ts # packages/goal/goal/tests/projection.spec.ts # packages/goal/tool-goal/tests/tool-goal.spec.ts # packages/host/apiproxy/src/api-proxy.ts # packages/host/apiproxy/src/api/events.schema.ts # packages/host/apiproxy/src/api/events.ts # packages/host/apiproxy/tests/api-proxy-workspace.spec.ts # packages/llm/llm/README.i18n.yaml # packages/llm/llm/README.zh.md # packages/llm/llm/src/index.ts # packages/pty/pty-local/tests/index.spec.ts # packages/pty/pty-local/tests/local.spec.ts # packages/pty/pty/tests/service.spec.ts # packages/pty/tool-pty/tests/loader-composition.spec.ts # packages/pty/tool-pty/tests/tools.spec.ts # packages/skill/tool-skill/tests/tool-skill.spec.ts # packages/tasks/tasks-local/tests/tasks.spec.ts # packages/ui/tui/src/index.ts # packages/ui/tui/tests/harness.ts # packages/ui/tui/tests/tui.spec.ts # scripts/gen-cordis-catalog.ts # scripts/type-equiv.manifest.json
This commit is contained in:
1175 files changed
+49683
-8452
No files matched your search
@@ -2,10 +2,12 @@
|
||||
* Schedules one assistant step's tool calls. Exclusive calls form barriers;
|
||||
* parallel calls use a bounded rolling pool and are reclassified before start.
|
||||
* Dispatch may overlap, while policy, results, and result context remain
|
||||
* model-ordered. Abort stops replenishment and drains started calls.
|
||||
* model-ordered. Abort or an internal scheduler failure stops replenishment
|
||||
* and drains started calls.
|
||||
*
|
||||
* Each advertised call records a balanced `tool/call`/`tool/result` pair. Calls
|
||||
* skipped after abort receive synthetic error results so replay stays valid.
|
||||
* Abort records synthetic error results for skipped calls so replay stays
|
||||
* valid. A terminal scheduler failure preserves already-recorded `tool/call`
|
||||
* events without fabricating results.
|
||||
* @module dsh-agent-loop/tool-calls
|
||||
*/
|
||||
|
||||
@@ -37,10 +39,13 @@ interface GroupOutcome {
|
||||
|
||||
/**
|
||||
* Schedule one assistant step's tool calls by their live concurrency mode.
|
||||
* Started calls receive ordered results. Abort drains them, records synthetic
|
||||
* results for unstarted calls, and returns with the signal still aborted after
|
||||
* accepting started-call context through the caller-supplied acceptor (the
|
||||
* machine stages it on its outbox for the next step boundary).
|
||||
* Ordinary completion and abort commit started-call results in order. Abort
|
||||
* drains them, records synthetic results for unstarted calls, and returns with
|
||||
* the signal still aborted after accepting started-call context through the
|
||||
* caller-supplied acceptor (the machine stages it on its outbox for the next
|
||||
* step boundary). An internal scheduler failure stops new dispatches, drains
|
||||
* already-started dispatches, and rejects with the first failure without
|
||||
* fabricating tool results.
|
||||
* The committed step's AgentLoop driver boundary supplies the initiating Agent
|
||||
* that becomes each explicit {@link ToolExecutionInput.agent}.
|
||||
*
|
||||
@@ -78,7 +83,7 @@ export async function executeToolCalls(
|
||||
let concluded = false
|
||||
while (next < planned.length) {
|
||||
// Commit before classifying again so registry changes affect unstarted calls.
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- bounded by the loop condition
|
||||
// oxlint-disable-next-line typescript/no-non-null-assertion -- bounded by the loop condition
|
||||
const first = planned[next]!
|
||||
const mode = ctx.tools.executionMode(first.exec).kind
|
||||
const group = mode === 'parallel' ? planned.slice(next) : [first]
|
||||
@@ -110,7 +115,8 @@ function parseArguments(raw: string): unknown {
|
||||
* drain and remains for the caller's next barrier. Results and contexts commit
|
||||
* in model order. Abort stops starts, drains and commits started calls, accepts
|
||||
* their contexts into the owning batch, records results for skipped calls, and
|
||||
* returns an aborted outcome.
|
||||
* returns an aborted outcome. Scheduler failure drains dispatches without
|
||||
* committing synthetic recovery results.
|
||||
*/
|
||||
async function runGroup(
|
||||
ctx: Context,
|
||||
@@ -131,6 +137,10 @@ async function runGroup(
|
||||
let started = 0
|
||||
let aborted: boolean = signal.aborted
|
||||
let concluded = false
|
||||
let schedulerFailure: { error: unknown } | undefined
|
||||
const throwSchedulerFailure = (): void => {
|
||||
if (schedulerFailure !== undefined) throw schedulerFailure.error
|
||||
}
|
||||
|
||||
// `committed` advances only across contiguous model-order slots.
|
||||
const commitReady = async (): Promise<void> => {
|
||||
@@ -141,7 +151,7 @@ async function runGroup(
|
||||
const result = slot.needsPost
|
||||
? await ctx.tools[TOOL_REGISTRY_SCHEDULER].finalize(slot.exec, slot.result)
|
||||
: ctx.tools[TOOL_REGISTRY_SCHEDULER].finish(slot.exec, slot.result)
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- bounded index
|
||||
// oxlint-disable-next-line typescript/no-non-null-assertion -- bounded index
|
||||
appendToolResult(session, turn, step, call!.block, result, callSeqs[committed]!)
|
||||
for (const context of result.additionalContexts ?? []) acceptContext(context)
|
||||
concluded ||= result.concludesTurn === true
|
||||
@@ -152,17 +162,24 @@ async function runGroup(
|
||||
const inFlight = new Map<number, Promise<number>>()
|
||||
|
||||
const startCall = async (index: number): Promise<void> => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- bounded index
|
||||
// oxlint-disable-next-line typescript/no-non-null-assertion -- bounded index
|
||||
const call = group[index]!
|
||||
callSeqs[index] = appendToolCall(session, turn, step, call.block)
|
||||
started++
|
||||
const prepared = await ctx.tools[TOOL_REGISTRY_SCHEDULER].prepare(call.exec)
|
||||
throwSchedulerFailure()
|
||||
switch (prepared.kind) {
|
||||
case 'dispatch': {
|
||||
const promise = ctx.tools[TOOL_REGISTRY_SCHEDULER].dispatch(prepared.exec).then((outcome) => {
|
||||
slots[index] = { exec: prepared.exec, result: outcome.result, needsPost: outcome.kind === 'post-result' }
|
||||
return index
|
||||
})
|
||||
const promise = ctx.tools[TOOL_REGISTRY_SCHEDULER].dispatch(prepared.exec).then(
|
||||
(outcome) => {
|
||||
slots[index] = { exec: prepared.exec, result: outcome.result, needsPost: outcome.kind === 'post-result' }
|
||||
return index
|
||||
},
|
||||
(error: unknown) => {
|
||||
schedulerFailure ??= { error }
|
||||
return index
|
||||
},
|
||||
)
|
||||
inFlight.set(index, promise)
|
||||
break
|
||||
}
|
||||
@@ -181,30 +198,40 @@ async function runGroup(
|
||||
const fillPool = async (): Promise<void> => {
|
||||
while (!aborted && nextToStart < group.length && inFlight.size < maxParallelToolCalls) {
|
||||
// Re-read later modes after ordered commits so registry changes can create a barrier.
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- bounded by the loop condition
|
||||
// oxlint-disable-next-line typescript/no-non-null-assertion -- bounded by the loop condition
|
||||
const nextCall = group[nextToStart]!
|
||||
if (nextToStart > 0 && mode === 'parallel'
|
||||
&& ctx.tools.executionMode(nextCall.exec).kind !== 'parallel') break
|
||||
await startCall(nextToStart)
|
||||
nextToStart++
|
||||
throwSchedulerFailure()
|
||||
await commitReady()
|
||||
throwSchedulerFailure()
|
||||
// Abort may arrive while pre-execute awaits.
|
||||
if (signal.aborted) aborted = true
|
||||
}
|
||||
}
|
||||
|
||||
// Ordered pre-execute may await; only dispatch/body overlaps.
|
||||
// TODO: Drain every started call before rethrowing a scheduler error; tool
|
||||
// bodies must not outlive the failed turn.
|
||||
await fillPool()
|
||||
while (inFlight.size > 0) {
|
||||
const settledIndex = await Promise.race(inFlight.values())
|
||||
inFlight.delete(settledIndex)
|
||||
await commitReady()
|
||||
// Abort may arrive while a tool or ordered commit awaits.
|
||||
|
||||
if (signal.aborted) aborted = true
|
||||
// Ordered pre-execute may await; only dispatch/body overlaps. A scheduler
|
||||
// failure stops new dispatches and reaches the turn boundary after every
|
||||
// already-started dispatch settles.
|
||||
try {
|
||||
await fillPool()
|
||||
while (inFlight.size > 0) {
|
||||
const settledIndex = await Promise.race(inFlight.values())
|
||||
inFlight.delete(settledIndex)
|
||||
throwSchedulerFailure()
|
||||
await commitReady()
|
||||
throwSchedulerFailure()
|
||||
// Abort may arrive while a tool or ordered commit awaits.
|
||||
|
||||
if (signal.aborted) aborted = true
|
||||
await fillPool()
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
schedulerFailure ??= { error }
|
||||
await Promise.allSettled(inFlight.values())
|
||||
throw schedulerFailure.error
|
||||
}
|
||||
|
||||
if (aborted) {
|
||||
|
||||
Reference in New Issue
Block a user