feat: optimize subagent list children query
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/subagent/subagent/README.md
|
||||
README.md: 4776f45a2f4ba881c2bb8414876100dc84adc86b
|
||||
README.zh.md: a40a12a4b386c91409711b8459a6c3b1f3f37cd0
|
||||
README.md: 9aea27a0f150d90a41d9a7cb4cd422a75e6107fe
|
||||
README.zh.md: 3f0b534deae53b8d5aff2765974050f26b931953
|
||||
|
||||
@@ -35,7 +35,7 @@ Multiple providers may coexist under different names. This lets a deployment exp
|
||||
| `reportFrom(child, content, { delivery, signal })` | Deliver one selected message from the exact live continuable child to its exact live direct parent and return the accepted stable `MessageId`. Quiet delivery injects context; waking delivery submits one later parent turn. |
|
||||
| `registerContinuableSetup(contribution)` | Compose an optional deployment capability into each continuable child's unpublished scope, with immediate revocation from resident children. |
|
||||
| `drainContinuableDescendants(parents)` | Close admission below exact live host-owned parent Agents, stop only their visible continuable descendants, await materializations admitted below those roots through publication or rollback, then release the selected forests child-first. The cutoff lasts until each exact parent leaves the registry; unrelated parent forests and manager-wide admission remain live. |
|
||||
| `listChildren(parentSessionId, signal?)` | List direct session-backed subagents with their `one-shot`/`continuable` mode and `running`/`inactive` activity, plus per-child diagnostics, in stable trace order without loading or resuming them. Requires session query; it does not require `ctx.agents` or the continuation manager. |
|
||||
| `listChildren(parentSessionId, signal?)` | List direct session-backed subagents with their `one-shot`/`continuable` mode, `running`/`inactive` activity, origin-classified one-level `hasChildren` hint, and per-child diagnostics, in stable trace order without loading or resuming them. Requires session query; it does not require `ctx.agents` or the continuation manager. |
|
||||
|
||||
`SubagentStartRequest.label` is an optional short durable display label for a session-backed one-shot child. Model-facing delegation supplies its existing `description`; lower-level callers need not invent presentation metadata. Continuable starts always carry their own required label. `signal` is required and is the canonical cancellation channel for a one-shot `start`. An abort before publication makes `start()` reject after rollback; an abort after publication cancels the returned run's remaining turn work without hiding its id. The request may also select a model, require structured output, cap delegation depth, restrict child tools, or set a child persona. For a continuable start or follow-up, the caller signal owns lookup, materialization, and admission only until inbox acceptance; afterward the manager owns the Activation independently, so later caller cancellation neither cancels the accepted turn nor disposes the child.
|
||||
|
||||
@@ -96,7 +96,7 @@ Continuable children do not create `SubagentRun` or Tasks. The continuation mana
|
||||
|
||||
## Collection model
|
||||
|
||||
The model-facing tool collects synchronously by default: it awaits the child result and disposes the run before returning. One-shot background delegation registers a plain Task in the tool, whose generic status, collection, and cancellation tools own later interaction, and persists its model-supplied `description` as the optional display label. Continuable background delegation calls `ctx.subagents.startContinuable()` and returns only the durable child id; the child owns its own turns from inbox acceptance, so there is no Task, no result promise, and no public subagent cancellation — a caller sends later work with the `send_message` follow-up tool, and the durable child Session remains the source of the child's detailed output. The continuation manager exists only while `ctx.agents` is available, and session persistence is resolved per continuation operation. Independently, `listChildren()` resolves session query and dynamically imports its optional runtime only when called, then interprets a read-only live-preferred scan of all descriptor-bearing direct children without consulting the continuation manager, Agent registrations, Activations, or providers. Service consumers such as a UI can retain both modes and choose a fallback for an unlabeled one-shot child; the model-facing `list_agents` tool projects only `continuable` entries and maps service activity to its existing `running`/`complete` vocabulary. The scan forwards the caller's signal to cancellable trace and exact-read operations, checks cancellation around the remaining event-list read, and reports every observed abort as `SubagentError` code `CANCELLED`. See the [background subagent tasks Agent Note](../../../.agents/notes/implemented/feature/2026-07-08-background-subagent-tasks.md), the [continuable background subagents Agent Note](../../../.agents/notes/implemented/feature/2026-07-21-continuable-background-subagents.md), the [durable catalog Agent Note](../../../.agents/notes/implemented/feature/2026-07-22-durable-subagent-catalog-and-list-agents.md), the [merged-service Agent Note](../../../.agents/notes/implemented/simplification/2026-07-26-merge-subagent-control-service.md), the [capability-seam Agent Note](../../../.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md), and `src/types.ts` for the complete contracts.
|
||||
The model-facing tool collects synchronously by default: it awaits the child result and disposes the run before returning. One-shot background delegation registers a plain Task in the tool, whose generic status, collection, and cancellation tools own later interaction, and persists its model-supplied `description` as the optional display label. Continuable background delegation calls `ctx.subagents.startContinuable()` and returns only the durable child id; the child owns its own turns from inbox acceptance, so there is no Task, no result promise, and no public subagent cancellation — a caller sends later work with the `send_message` follow-up tool, and the durable child Session remains the source of the child's detailed output. The continuation manager exists only while `ctx.agents` is available, and session persistence is resolved per continuation operation. Independently, `listChildren()` resolves session query and dynamically imports its optional runtime only when called, then interprets a read-only live-preferred scan of all descriptor-bearing direct children without consulting the continuation manager, Agent registrations, Activations, or providers. Each healthy row derives its read-time `hasChildren` hint from traced direct-descendant headers carrying durable `origin: 'subagent'`; it does not read descendant event logs, and the descriptor-backed child catalog remains authoritative when expanded. Service consumers such as a UI can retain both modes and choose a fallback for an unlabeled one-shot child; the model-facing `list_agents` tool projects only `continuable` entries and maps service activity to its existing `running`/`complete` vocabulary. The scan forwards the caller's signal to cancellable trace and exact-read operations, checks cancellation around the remaining event-list read, and reports every observed abort as `SubagentError` code `CANCELLED`. See the [background subagent tasks Agent Note](../../../.agents/notes/implemented/feature/2026-07-08-background-subagent-tasks.md), the [continuable background subagents Agent Note](../../../.agents/notes/implemented/feature/2026-07-21-continuable-background-subagents.md), the [durable catalog Agent Note](../../../.agents/notes/implemented/feature/2026-07-22-durable-subagent-catalog-and-list-agents.md), the [merged-service Agent Note](../../../.agents/notes/implemented/simplification/2026-07-26-merge-subagent-control-service.md), the [capability-seam Agent Note](../../../.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md), and `src/types.ts` for the complete contracts.
|
||||
|
||||
Continuable Activations await a best-effort final session flush without treating listener participation as durability confirmation. One-shot runs retain best-effort session checkpointing, so a completed one-shot child is discoverable after disposal only when its session actually reached persistence; the service does not invent a catalog entry from Task history when that checkpoint is absent.
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ subagent seam 允许一个 agent(智能体)通过具名提供方把工作委
|
||||
| `reportFrom(child, content, { delivery, signal })` | 从确切在线可继续 child 向其确切在线直接 parent 投递一条选中消息,并返回已接受的稳定 `MessageId`。静默投递会注入上下文;唤醒投递会提交一个后续 parent 轮次。 |
|
||||
| `registerContinuableSetup(contribution)` | 把一项可选部署能力组合到每个可继续 child 尚未发布的作用域中,并支持从驻留 child 立即撤销。 |
|
||||
| `drainContinuableDescendants(parents)` | 在由 host 确切拥有的在线 parent Agent 之下关闭准入,只停止其可见的可继续后代,等待在这些根之下已获准的物化过程完成发布或回滚,再按 child-first 顺序释放所选森林。该截止状态会持续到每个确切 parent 离开注册表;无关的 parent 森林和管理器全局准入保持在线。 |
|
||||
| `listChildren(parentSessionId, signal?)` | 按稳定的追踪顺序列出由会话支撑的直接 subagent,包括其 `one-shot`/`continuable` 模式和 `running`/`inactive` 活动状态,以及逐 child diagnostic,且不会加载或恢复它们。要求会话查询;不要求 `ctx.agents` 或继续执行管理器。 |
|
||||
| `listChildren(parentSessionId, signal?)` | 按稳定的追踪顺序列出由会话支撑的直接 subagent,包括其 `one-shot`/`continuable` 模式、`running`/`inactive` 活动状态、基于 origin 分类的一层 `hasChildren` 提示与逐 child diagnostic,且不会加载或恢复它们。要求会话查询;不要求 `ctx.agents` 或继续执行管理器。 |
|
||||
|
||||
`SubagentStartRequest.label` 是由会话支撑的一次性 child 所使用的可选简短持久化显示标签。面向模型的委派会提供其已有的 `description`;底层调用方无需凭空构造展示元数据。可继续启动始终携带自身的必填标签。`signal` 是必填项,也是一次性 `start` 的规范取消通道。发布前中止会使 `start()` 在回滚后拒绝;发布后中止会取消已返回 run 的剩余轮次工作,但不会隐藏其 id。请求还可以选择模型、要求结构化输出、限制委派深度、约束子 agent 工具或设置子 agent persona。对于可继续启动或后续操作,调用方信号只在 inbox 接受之前掌管查找、物化和准入;此后由管理器独立拥有 Activation,因此调用方后续取消既不会取消已接受的轮次,也不会 dispose 子 agent。
|
||||
|
||||
@@ -96,7 +96,7 @@ subagent seam 允许一个 agent(智能体)通过具名提供方把工作委
|
||||
|
||||
## 收集模型
|
||||
|
||||
面向模型的工具默认同步收集:先等待子 agent 结果,再 dispose 运行,然后才返回。一次性后台委派会在工具中注册普通 Task,其通用状态、收集和取消工具负责后续交互,并将模型提供的 `description` 持久化为可选显示标签。可继续后台委派会调用 `ctx.subagents.startContinuable()`,只返回持久化子 agent id;子 agent 自 inbox 接受起就拥有自己的轮次,因此没有 Task、没有结果 promise,也没有公开的子 agent 取消操作——调用方通过 `send_message` 后续操作工具发送后续工作,而持久化子 agent Session 仍是子 agent 详细输出的来源。只有 `ctx.agents` 可用时,继续执行管理器才会存在,而会话持久化按每项继续执行操作解析。与此独立,`listChildren()` 只在被调用时解析会话查询并动态导入其可选运行时,然后解释对所有带描述符的直接 child 所作的只读、实时优先扫描,且不查询继续执行管理器、Agent 注册信息、Activation 或提供方。UI 等服务消费方可以保留两种模式,并为无标签的一次性 child 选择回退展示;面向模型的 `list_agents` 工具只投影 `continuable` 条目,并将服务活动状态映射到现有的 `running`/`complete` 词汇。扫描会把调用方的取消信号转发到可取消的追踪与精确读取操作,在其余事件列表读取的前后检查取消,并将每次检测到的中止报告为 `SubagentError` 错误码 `CANCELLED`。完整契约见[后台 subagent 任务 Agent Note](../../../.agents/notes/implemented/feature/2026-07-08-background-subagent-tasks.md)、[可继续后台 subagent Agent Note](../../../.agents/notes/implemented/feature/2026-07-21-continuable-background-subagents.md)、[持久化目录 Agent Note](../../../.agents/notes/implemented/feature/2026-07-22-durable-subagent-catalog-and-list-agents.md)、[服务合并 Agent Note](../../../.agents/notes/implemented/simplification/2026-07-26-merge-subagent-control-service.md)、[能力 seam Agent Note](../../../.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md)和 `src/types.ts`。
|
||||
面向模型的工具默认同步收集:先等待子 agent 结果,再 dispose 运行,然后才返回。一次性后台委派会在工具中注册普通 Task,其通用状态、收集和取消工具负责后续交互,并将模型提供的 `description` 持久化为可选显示标签。可继续后台委派会调用 `ctx.subagents.startContinuable()`,只返回持久化子 agent id;子 agent 自 inbox 接受起就拥有自己的轮次,因此没有 Task、没有结果 promise,也没有公开的子 agent 取消操作——调用方通过 `send_message` 后续操作工具发送后续工作,而持久化子 agent Session 仍是子 agent 详细输出的来源。只有 `ctx.agents` 可用时,继续执行管理器才会存在,而会话持久化按每项继续执行操作解析。与此独立,`listChildren()` 只在被调用时解析会话查询并动态导入其可选运行时,然后解释对所有带描述符的直接 child 所作的只读、实时优先扫描,且不查询继续执行管理器、Agent 注册信息、Activation 或提供方。每个健康条目都会根据追踪结果中携带持久化 `origin: 'subagent'` 的直接后代 header 派生读取时的 `hasChildren` 提示;它不会读取后代事件日志,展开后仍以描述符支撑的 child 目录为权威依据。UI 等服务消费方可以保留两种模式,并为无标签的一次性 child 选择回退展示;面向模型的 `list_agents` 工具只投影 `continuable` 条目,并将服务活动状态映射到现有的 `running`/`complete` 词汇。扫描会把调用方的取消信号转发到可取消的追踪与精确读取操作,在其余事件列表读取的前后检查取消,并将每次检测到的中止报告为 `SubagentError` 错误码 `CANCELLED`。完整契约见[后台 subagent 任务 Agent Note](../../../.agents/notes/implemented/feature/2026-07-08-background-subagent-tasks.md)、[可继续后台 subagent Agent Note](../../../.agents/notes/implemented/feature/2026-07-21-continuable-background-subagents.md)、[持久化目录 Agent Note](../../../.agents/notes/implemented/feature/2026-07-22-durable-subagent-catalog-and-list-agents.md)、[服务合并 Agent Note](../../../.agents/notes/implemented/simplification/2026-07-26-merge-subagent-control-service.md)、[能力 seam Agent Note](../../../.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md)和 `src/types.ts`。
|
||||
|
||||
可继续 Activation 会等待 best-effort 的最终会话 flush,但不会把 listener 参与视为持久性确认。一次性运行保留尽力执行的会话检查点,因此已完成的一次性 child 只有在其会话确实进入持久化存储时,才可在 dispose 后继续被发现;如果该检查点缺失,服务不会根据 Task 历史虚构目录条目。
|
||||
|
||||
|
||||
@@ -22,9 +22,10 @@ type SessionQueryRuntime = Pick<
|
||||
/**
|
||||
* One entry of a {@link listChildren} result in trace candidate order. A valid
|
||||
* descriptor produces a `child`, a per-child inspection failure produces a
|
||||
* `diagnostic`, and a descriptor-less ordinary child is omitted. Diagnostics
|
||||
* are transient query results, never session events or catalog state, and
|
||||
* never expose model-hidden descriptor content.
|
||||
* `diagnostic`, and a descriptor-less ordinary child is omitted. Healthy rows
|
||||
* include a one-level, origin-classified descendant hint. Diagnostics are
|
||||
* transient query results, never session events or catalog state, and never
|
||||
* expose model-hidden descriptor content.
|
||||
*/
|
||||
export type SubagentListEntry =
|
||||
| {
|
||||
@@ -38,6 +39,8 @@ export type SubagentListEntry =
|
||||
* delivery as an ownership conflict.
|
||||
*/
|
||||
readonly activity: 'running' | 'inactive'
|
||||
/** Whether a direct descendant has durable `origin: 'subagent'`. */
|
||||
readonly hasChildren: boolean
|
||||
} & (
|
||||
| {
|
||||
/** A terminal one-shot child. */
|
||||
@@ -100,7 +103,12 @@ export async function listChildren(
|
||||
)
|
||||
const entries: SubagentListEntry[] = []
|
||||
for (const node of trace.descendants) {
|
||||
const entry = await inspectChild(query, queryRuntime, parentSessionId, node.session, signal)
|
||||
const hasChildren = node.descendants.some(
|
||||
descendant => descendant.session.header.origin === 'subagent',
|
||||
)
|
||||
const entry = await inspectChild(
|
||||
query, queryRuntime, parentSessionId, node.session, hasChildren, signal,
|
||||
)
|
||||
// Cancellation can race the inspection's last checkpoint or diagnostic
|
||||
// mapping; do not return success or begin another candidate afterward.
|
||||
assertListingNotCancelled(signal)
|
||||
@@ -115,6 +123,7 @@ async function inspectChild(
|
||||
queryRuntime: SessionQueryRuntime,
|
||||
parentSessionId: SessionId,
|
||||
candidate: SessionRecord,
|
||||
hasChildren: boolean,
|
||||
signal?: AbortSignal,
|
||||
): Promise<SubagentListEntry | undefined> {
|
||||
const childId = candidate.header.id
|
||||
@@ -158,9 +167,13 @@ async function inspectChild(
|
||||
mode: descriptor.mode,
|
||||
...descriptor.label !== undefined ? { label: descriptor.label } : {},
|
||||
activity,
|
||||
hasChildren,
|
||||
}
|
||||
}
|
||||
return { kind: 'child', id: childId, mode: descriptor.mode, label: descriptor.label, activity }
|
||||
return {
|
||||
kind: 'child', id: childId, mode: descriptor.mode, label: descriptor.label,
|
||||
activity, hasChildren,
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
const reason = perChildDiagnosticReason(error, queryRuntime.SessionQueryError)
|
||||
if (reason === undefined) throw error
|
||||
|
||||
@@ -121,7 +121,10 @@ describe('SubagentService.listChildren', () => {
|
||||
child.append('subagent/descriptor', descriptorPayload('query-only child'))
|
||||
|
||||
await expect(ctx.subagents.listChildren(parentId)).resolves.toEqual([
|
||||
{ kind: 'child', id: childId, label: 'query-only child', mode: 'continuable', activity: 'running' },
|
||||
{
|
||||
kind: 'child', id: childId, label: 'query-only child', mode: 'continuable',
|
||||
activity: 'running', hasChildren: false,
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
@@ -137,7 +140,10 @@ describe('SubagentService.listChildren', () => {
|
||||
const childId = await startChild(ctx, parent, 'summarize the doc')
|
||||
const entries = await ctx.subagents.listChildren(parent.id)
|
||||
expect(entries).toEqual([
|
||||
{ kind: 'child', id: childId, label: 'summarize the doc', mode: 'continuable', activity: 'inactive' },
|
||||
{
|
||||
kind: 'child', id: childId, label: 'summarize the doc', mode: 'continuable',
|
||||
activity: 'inactive', hasChildren: false,
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
@@ -160,6 +166,7 @@ describe('SubagentService.listChildren', () => {
|
||||
id: oneShotId,
|
||||
mode: 'one-shot',
|
||||
activity: 'inactive',
|
||||
hasChildren: false,
|
||||
})
|
||||
expect(entries).toContainEqual({
|
||||
kind: 'child',
|
||||
@@ -167,6 +174,7 @@ describe('SubagentService.listChildren', () => {
|
||||
label: 'continuable child',
|
||||
mode: 'continuable',
|
||||
activity: 'inactive',
|
||||
hasChildren: false,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -188,7 +196,10 @@ describe('SubagentService.listChildren', () => {
|
||||
}, childEvents(descriptorPayload('persisted parent case')))
|
||||
const entries = await ctx.subagents.listChildren(coldParent)
|
||||
expect(entries).toEqual([
|
||||
{ kind: 'child', id: childId, label: 'persisted parent case', mode: 'continuable', activity: 'inactive' },
|
||||
{
|
||||
kind: 'child', id: childId, label: 'persisted parent case', mode: 'continuable',
|
||||
activity: 'inactive', hasChildren: false,
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
@@ -227,10 +238,12 @@ describe('SubagentService.listChildren', () => {
|
||||
live.append('subagent/descriptor', descriptorPayload('live child'))
|
||||
const entries = await ctx.subagents.listChildren(parent.id)
|
||||
expect(entries).toContainEqual({
|
||||
kind: 'child', id: settled, label: 'settled child', mode: 'continuable', activity: 'inactive',
|
||||
kind: 'child', id: settled, label: 'settled child', mode: 'continuable',
|
||||
activity: 'inactive', hasChildren: false,
|
||||
})
|
||||
expect(entries).toContainEqual({
|
||||
kind: 'child', id: liveId, label: 'live child', mode: 'continuable', activity: 'running',
|
||||
kind: 'child', id: liveId, label: 'live child', mode: 'continuable',
|
||||
activity: 'running', hasChildren: false,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -251,7 +264,8 @@ describe('SubagentService.listChildren', () => {
|
||||
const entries = await ctx.subagents.listChildren(parent.id)
|
||||
expect(entries).toContainEqual({ kind: 'diagnostic', id: corrupt, reason: 'corrupt' })
|
||||
expect(entries).toContainEqual({
|
||||
kind: 'child', id: healthy, label: 'healthy sibling', mode: 'continuable', activity: 'inactive',
|
||||
kind: 'child', id: healthy, label: 'healthy sibling', mode: 'continuable',
|
||||
activity: 'inactive', hasChildren: false,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -318,7 +332,10 @@ describe('SubagentService.listChildren', () => {
|
||||
}))
|
||||
const entries = await ctx.subagents.listChildren(parent.id)
|
||||
expect(entries).toEqual([
|
||||
{ kind: 'child', id: foreign, label: 'orphan provider', mode: 'continuable', activity: 'inactive' },
|
||||
{
|
||||
kind: 'child', id: foreign, label: 'orphan provider', mode: 'continuable',
|
||||
activity: 'inactive', hasChildren: false,
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
@@ -433,21 +450,73 @@ describe('SubagentService.listChildren', () => {
|
||||
}, compactedEvents)
|
||||
const entries = await ctx.subagents.listChildren(parent.id)
|
||||
expect(entries).toEqual([
|
||||
{ kind: 'child', id: plain, label: 'twin child', mode: 'continuable', activity: 'inactive' },
|
||||
{ kind: 'child', id: compacted, label: 'twin child', mode: 'continuable', activity: 'inactive' },
|
||||
{
|
||||
kind: 'child', id: plain, label: 'twin child', mode: 'continuable',
|
||||
activity: 'inactive', hasChildren: false,
|
||||
},
|
||||
{
|
||||
kind: 'child', id: compacted, label: 'twin child', mode: 'continuable',
|
||||
activity: 'inactive', hasChildren: false,
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('excludes grandchildren: only direct descendants are candidates', async () => {
|
||||
it('reports an origin-classified grandchild without reading its events', async () => {
|
||||
const { ctx, parent } = await setup([textResponse('done')])
|
||||
const childId = await startChild(ctx, parent, 'direct child')
|
||||
await authorChild(ctx, '00000000-0000-4000-8000-0000000000cc', {
|
||||
const grandchildId = await authorChild(ctx, '00000000-0000-4000-8000-0000000000cc', {
|
||||
parentSession: childId,
|
||||
origin: 'subagent',
|
||||
}, childEvents(descriptorPayload('grandchild')))
|
||||
const query = ctx.get('sessionQuery')!
|
||||
const originalListEvents = query.listEvents.bind(query)
|
||||
const inspected: SessionId[] = []
|
||||
query.listEvents = (sessionId) => {
|
||||
inspected.push(sessionId)
|
||||
return originalListEvents(sessionId)
|
||||
}
|
||||
const entries = await ctx.subagents.listChildren(parent.id)
|
||||
expect(entries).toEqual([
|
||||
{ kind: 'child', id: childId, label: 'direct child', mode: 'continuable', activity: 'inactive' },
|
||||
{
|
||||
kind: 'child', id: childId, label: 'direct child', mode: 'continuable',
|
||||
activity: 'inactive', hasChildren: true,
|
||||
},
|
||||
])
|
||||
expect(inspected).toContain(childId)
|
||||
expect(inspected).not.toContain(grandchildId)
|
||||
})
|
||||
|
||||
it('does not count an ordinary grandchild without subagent origin', async () => {
|
||||
const { ctx, parent } = await setup([textResponse('done')])
|
||||
const childId = await startChild(ctx, parent, 'direct child')
|
||||
await authorChild(ctx, '00000000-0000-4000-8000-0000000000f1', {
|
||||
parentSession: childId,
|
||||
}, [
|
||||
{ type: 'turn/start', seq: 0, time: 1, data: { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } } },
|
||||
{ type: 'turn/end', seq: 1, time: 2, data: { turn: 1, reason: { kind: 'completed' } } },
|
||||
] as SessionEvent[])
|
||||
|
||||
await expect(ctx.subagents.listChildren(parent.id)).resolves.toEqual([{
|
||||
kind: 'child', id: childId, label: 'direct child', mode: 'continuable',
|
||||
activity: 'inactive', hasChildren: false,
|
||||
}])
|
||||
})
|
||||
|
||||
it('counts an origin-classified diagnostic grandchild', async () => {
|
||||
const { ctx, parent } = await setup([textResponse('done')])
|
||||
const childId = await startChild(ctx, parent, 'direct child')
|
||||
const diagnosticId = await authorChild(ctx, '00000000-0000-4000-8000-0000000000f2', {
|
||||
parentSession: childId,
|
||||
origin: 'subagent',
|
||||
}, childEvents({ version: SUBAGENT_DESCRIPTOR_VERSION, mode: 'continuable', provider: 7 }))
|
||||
|
||||
await expect(ctx.subagents.listChildren(childId)).resolves.toEqual([
|
||||
{ kind: 'diagnostic', id: diagnosticId, reason: 'corrupt' },
|
||||
])
|
||||
await expect(ctx.subagents.listChildren(parent.id)).resolves.toEqual([{
|
||||
kind: 'child', id: childId, label: 'direct child', mode: 'continuable',
|
||||
activity: 'inactive', hasChildren: true,
|
||||
}])
|
||||
})
|
||||
|
||||
it('stops the scan at the between-candidates checkpoint when the signal aborts', async () => {
|
||||
|
||||
Reference in New Issue
Block a user