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:
kingwl
2026-08-02 23:37:17 +08:00
102 changed files with 1586 additions and 291 deletions

View File

@@ -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
},
}

View File

@@ -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)

View File

@@ -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
*/

View File

@@ -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', () => {