Merge master into subagent usage branch
# Conflicts: # .agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.i18n.yaml # .agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.md # .agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.zh.md # apps/web/tests/snapshots/subagent-conversation/tree.expected.md # apps/web/tests/subagent-conversation.e2e.ts # packages/client/ui-subagent/src/client/SubagentCatalogAction.tsx
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
*/
|
||||
|
||||
import type { Context } from 'cordis'
|
||||
import type { AgentSetupCommit } from '@deepseek-ai/dsh-agent'
|
||||
import { errorChain } from '@deepseek-ai/dsh-llm'
|
||||
import { SubagentError } from './error.ts'
|
||||
|
||||
@@ -47,17 +48,6 @@ interface TransactionState {
|
||||
invalidated: boolean
|
||||
}
|
||||
|
||||
/** Package-private setup transaction consumed by the continuation manager. */
|
||||
export interface ActivationSetupTransaction {
|
||||
/**
|
||||
* Reject a batch invalidated by revocation before publication.
|
||||
* @throws {SubagentError} code `ACTIVATION_SETUP_REVOKED` after revocation.
|
||||
*/
|
||||
assertIntact(): void
|
||||
/** Promote this batch to resident installations. */
|
||||
commit(): void
|
||||
}
|
||||
|
||||
/** Re-read mutable removal state after a contribution may have revoked itself. */
|
||||
function isRemoved(registration: Registration): boolean {
|
||||
return registration.removed
|
||||
@@ -95,9 +85,9 @@ export class SubagentActivationSetupRegistry {
|
||||
/**
|
||||
* Install every live contribution into one unpublished child context.
|
||||
* @param childCtx - the child's unpublished scoped context.
|
||||
* @returns the provisioning transaction.
|
||||
* @returns the provisioning commit consumed at Agent publication.
|
||||
*/
|
||||
apply(childCtx: Context): ActivationSetupTransaction {
|
||||
apply(childCtx: Context): AgentSetupCommit {
|
||||
const state: TransactionState = { installations: [], invalidated: false }
|
||||
try {
|
||||
for (const registration of [...this.registrations]) {
|
||||
@@ -135,15 +125,14 @@ export class SubagentActivationSetupRegistry {
|
||||
}
|
||||
childCtx.effect(() => () => { this.releaseChild(childCtx) }, 'subagents.activationSetup()')
|
||||
return {
|
||||
assertIntact: () => {
|
||||
if (!state.invalidated) return
|
||||
throw new SubagentError(
|
||||
'a continuable-subagent setup contribution was revoked while this child was being built; '
|
||||
+ 'the child was not established',
|
||||
'ACTIVATION_SETUP_REVOKED',
|
||||
)
|
||||
},
|
||||
commit: () => {
|
||||
if (state.invalidated) {
|
||||
throw new SubagentError(
|
||||
'a continuable-subagent setup contribution was revoked while this child was being built; '
|
||||
+ 'the child was not established',
|
||||
'ACTIVATION_SETUP_REVOKED',
|
||||
)
|
||||
}
|
||||
for (const installation of state.installations) installation.transaction = undefined
|
||||
},
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import type {
|
||||
Agent,
|
||||
AgentHandle,
|
||||
AgentOptions,
|
||||
AgentSetupCommit,
|
||||
CreateAgentOptions,
|
||||
} from '@deepseek-ai/dsh-agent'
|
||||
import { createUserMessage, errorChain } from '@deepseek-ai/dsh-llm'
|
||||
@@ -42,7 +43,6 @@ import type { ContinuableCreateRequest, ContinuableCreateSpec, SubagentStartRequ
|
||||
import type { ActivationObserver } from './lifecycle.ts'
|
||||
import { SubagentError } from './error.ts'
|
||||
import type SubagentActivationSetupRegistry from './activation-setup-registry.ts'
|
||||
import type { ActivationSetupTransaction } from './activation-setup-registry.ts'
|
||||
|
||||
/** Attribution for a model coordinator's follow-up to one of its children. */
|
||||
export interface CoordinatorMessageSource {
|
||||
@@ -800,10 +800,9 @@ export class SubagentContinuationManager {
|
||||
// `AgentRegistry.enter()` is the authoritative collision boundary for an id
|
||||
// some other owner holds — a duplicate would reject there with rollback.
|
||||
inputs.signal.throwIfAborted()
|
||||
let setupTransaction!: ActivationSetupTransaction
|
||||
const setup = (childCtx: Context): void => {
|
||||
const setup = (childCtx: Context): AgentSetupCommit => {
|
||||
applyChildComposition(childCtx, inputs.composition)
|
||||
setupTransaction = this.setupRegistry.apply(childCtx)
|
||||
return this.setupRegistry.apply(childCtx)
|
||||
}
|
||||
const observer = this.host.observeActivation(provider, childId, parent)
|
||||
const { create } = inputs
|
||||
@@ -842,7 +841,6 @@ export class SubagentContinuationManager {
|
||||
try {
|
||||
inputs.signal.throwIfAborted()
|
||||
this.assertAdmitting(parent)
|
||||
setupTransaction.assertIntact()
|
||||
this.acquireOwnership(parent, childId)
|
||||
// Every accepted id leaves the inbox exactly once, through dequeue or
|
||||
// discard. Clearing it there is what lets `stateOf()` distinguish a truly
|
||||
@@ -860,8 +858,8 @@ export class SubagentContinuationManager {
|
||||
for (const item of items) activation.accepted.delete(item.message.id)
|
||||
this.wake(activation)
|
||||
})
|
||||
// Resident setup revokes live from here instead of invalidating creation.
|
||||
setupTransaction.commit()
|
||||
// Agent creation committed setup at its publication boundary;
|
||||
// revocations from here on are immediate live revocation.
|
||||
// Publish the start edge before any turn can run, so observers see this
|
||||
// epoch before its first request.
|
||||
observer.start(handle.agent)
|
||||
|
||||
@@ -12,6 +12,11 @@
|
||||
* omits `subagentDepth` — cold resume trusts the persisted header's
|
||||
* `delegationDepth` as the monotone floor — and `outputSchema`, which belongs
|
||||
* to one activation's result contract rather than durable child composition.
|
||||
* Per-activation knobs such as `maxTokens` are omitted for the same reason as
|
||||
* `outputSchema`: they budget one activation. Cold resume requires the exact
|
||||
* live parent for authorization but reconstructs child options only from the
|
||||
* durable descriptor, so it neither restores the prior budget nor inherits
|
||||
* the parent's current one; the resumed route's defaults apply instead.
|
||||
*
|
||||
* @module @deepseek-ai/dsh-subagent/descriptor
|
||||
*/
|
||||
|
||||
@@ -19,8 +19,7 @@ describe('SubagentActivationSetupRegistry', () => {
|
||||
|
||||
const transaction = registry.apply(child.ctx)
|
||||
expect(order).toEqual(['first', 'second'])
|
||||
expect(() => { transaction.assertIntact() }).not.toThrow()
|
||||
transaction.commit()
|
||||
expect(() => { transaction.commit() }).not.toThrow()
|
||||
expect(order).toEqual(['first', 'second'])
|
||||
})
|
||||
|
||||
@@ -68,7 +67,7 @@ describe('SubagentActivationSetupRegistry', () => {
|
||||
|
||||
remove()
|
||||
expect(disposals).toBe(1)
|
||||
expect(() => { transaction.assertIntact() }).toThrow(/revoked while this child was being built/)
|
||||
expect(() => { transaction.commit() }).toThrow(/revoked while this child was being built/)
|
||||
})
|
||||
|
||||
it('catches a contribution revoked inside its own installer', () => {
|
||||
@@ -82,7 +81,7 @@ describe('SubagentActivationSetupRegistry', () => {
|
||||
|
||||
const transaction = registry.apply(childContext().ctx)
|
||||
expect(disposals).toBe(1)
|
||||
expect(() => { transaction.assertIntact() }).toThrow(/revoked/)
|
||||
expect(() => { transaction.commit() }).toThrow(/revoked/)
|
||||
})
|
||||
|
||||
it('attempts every contribution-removal disposer before reporting failures', () => {
|
||||
|
||||
Reference in New Issue
Block a user