@armadra/agent 0.6.1 → 0.6.3
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 +80 -0
- package/CHANGELOG.zh-CN.md +56 -0
- package/README.md +168 -575
- package/README.zh-CN.md +165 -596
- package/dist/agent/session-settings.d.ts +4 -1
- package/dist/agent/session-settings.js +7 -2
- package/dist/agent/session.d.ts +6 -0
- package/dist/agent/session.js +8 -0
- package/dist/agent/subagent-background.d.ts +75 -0
- package/dist/agent/subagent-background.js +209 -0
- package/dist/agent/subagent-registry.d.ts +24 -5
- package/dist/agent/subagent-registry.js +64 -92
- package/dist/agent/types-w5.d.ts +11 -1
- package/dist/agent/types.d.ts +5 -0
- package/dist/agents/builtin.js +0 -1
- package/dist/agents/external.js +0 -1
- package/dist/agents/parse.js +4 -3
- package/dist/agents/result.d.ts +7 -1
- package/dist/agents/result.js +20 -1
- package/dist/agents/task-control.d.ts +13 -2
- package/dist/agents/task-record.d.ts +13 -1
- package/dist/agents/task-record.js +32 -0
- package/dist/agents/types.d.ts +5 -1
- package/dist/ai/apis/chatgpt-backend.d.ts +7 -2
- package/dist/ai/apis/chatgpt-backend.js +12 -4
- package/dist/ai/providers/discovered-cache.d.ts +29 -12
- package/dist/ai/providers/discovered-cache.js +72 -12
- package/dist/ai/providers/registry.js +7 -6
- package/dist/ai/types.d.ts +5 -0
- package/dist/auth/chatgpt/backend-client.d.ts +14 -4
- package/dist/auth/chatgpt/backend-client.js +41 -8
- package/dist/auth/chatgpt/cli.d.ts +2 -1
- package/dist/auth/chatgpt/cli.js +14 -7
- package/dist/auth/chatgpt/follow.d.ts +16 -0
- package/dist/auth/chatgpt/follow.js +51 -0
- package/dist/auth/chatgpt/presets.d.ts +8 -0
- package/dist/auth/chatgpt/presets.js +12 -0
- package/dist/auth/testing/fake-oauth.d.ts +4 -1
- package/dist/auth/testing/fake-oauth.js +24 -1
- package/dist/bundle/ama.cjs +2016 -664
- package/dist/cli/compose-agents.d.ts +2 -1
- package/dist/cli/compose-agents.js +11 -2
- package/dist/cli/compose-providers.d.ts +2 -1
- package/dist/cli/compose-providers.js +6 -3
- package/dist/cli/startup-steps.js +4 -1
- package/dist/cli/subcommands/models-discover.d.ts +5 -2
- package/dist/cli/subcommands/models-discover.js +52 -10
- package/dist/config/json-schema.js +10 -2
- package/dist/config/key-docs.js +9 -3
- package/dist/config/merge.d.ts +1 -1
- package/dist/config/merge.js +20 -3
- package/dist/config/schema-w5.js +4 -2
- package/dist/config/schema-w6.d.ts +1 -1
- package/dist/config/schema-w6.js +2 -1
- package/dist/config/schema.js +2 -0
- package/dist/config/settings-registry.js +4 -0
- package/dist/config/types-w5.d.ts +10 -0
- package/dist/config/types-w5.js +2 -0
- package/dist/config/types-w6.d.ts +5 -0
- package/dist/config/types.d.ts +3 -1
- package/dist/git/info.d.ts +19 -0
- package/dist/git/info.js +64 -8
- package/dist/i18n/catalog.d.ts +52 -8
- package/dist/i18n/messages/agents.d.ts +56 -0
- package/dist/i18n/messages/agents.js +58 -2
- package/dist/i18n/messages/auth.d.ts +12 -0
- package/dist/i18n/messages/auth.js +20 -0
- package/dist/i18n/messages/config-keys.d.ts +8 -0
- package/dist/i18n/messages/config-keys.js +18 -10
- package/dist/i18n/messages/config.d.ts +8 -0
- package/dist/i18n/messages/interactive-startup.d.ts +0 -16
- package/dist/i18n/messages/interactive-startup.js +0 -16
- package/dist/i18n/messages/interactive.d.ts +23 -16
- package/dist/i18n/messages/interactive.js +25 -0
- package/dist/i18n/messages/print.d.ts +4 -0
- package/dist/i18n/messages/print.js +4 -0
- package/dist/i18n/messages/report.js +4 -4
- package/dist/i18n/messages/settings.d.ts +6 -0
- package/dist/i18n/messages/settings.js +6 -0
- package/dist/i18n/messages/subcommands-config.d.ts +4 -0
- package/dist/i18n/messages/subcommands-config.js +4 -0
- package/dist/i18n/messages/subcommands.d.ts +4 -0
- package/dist/index.d.ts +1 -0
- package/dist/modes/commands-core.js +8 -4
- package/dist/modes/interactive/agent-bar.d.ts +3 -1
- package/dist/modes/interactive/agent-bar.js +12 -5
- package/dist/modes/interactive/agent-ui.d.ts +21 -3
- package/dist/modes/interactive/agent-ui.js +81 -12
- package/dist/modes/interactive/agent-view.d.ts +4 -0
- package/dist/modes/interactive/agent-view.js +14 -1
- package/dist/modes/interactive/approval-dock.d.ts +51 -0
- package/dist/modes/interactive/approval-dock.js +112 -0
- package/dist/modes/interactive/approval-ui.d.ts +43 -0
- package/dist/modes/interactive/approval-ui.js +64 -0
- package/dist/modes/interactive/commands.js +4 -1
- package/dist/modes/interactive/event-notices.d.ts +6 -1
- package/dist/modes/interactive/event-notices.js +7 -1
- package/dist/modes/interactive/interactive-mode.d.ts +4 -2
- package/dist/modes/interactive/interactive-mode.js +33 -38
- package/dist/modes/interactive/key-dispatch.d.ts +21 -4
- package/dist/modes/interactive/key-dispatch.js +60 -8
- package/dist/modes/interactive/line/line-mode.js +5 -0
- package/dist/modes/interactive/run-indicator.d.ts +15 -0
- package/dist/modes/interactive/run-indicator.js +37 -5
- package/dist/modes/interactive/session-events.js +5 -1
- package/dist/modes/interactive/startup-header.d.ts +29 -14
- package/dist/modes/interactive/startup-header.js +93 -59
- package/dist/modes/interactive/startup-logo.d.ts +83 -0
- package/dist/modes/interactive/startup-logo.js +183 -0
- package/dist/modes/interactive/status-area.d.ts +18 -0
- package/dist/modes/interactive/status-area.js +67 -1
- package/dist/modes/interactive/status-bar.d.ts +9 -1
- package/dist/modes/interactive/status-bar.js +43 -11
- package/dist/modes/interactive/status-line.d.ts +2 -0
- package/dist/modes/interactive/status-line.js +11 -6
- package/dist/modes/interactive/status-quota.d.ts +44 -0
- package/dist/modes/interactive/status-quota.js +135 -0
- package/dist/modes/interactive/subagent-view.d.ts +1 -0
- package/dist/modes/interactive/subagent-view.js +8 -0
- package/dist/modes/interactive/task-background.d.ts +31 -0
- package/dist/modes/interactive/task-background.js +68 -0
- package/dist/modes/interactive/tool-view.d.ts +7 -1
- package/dist/modes/interactive/tool-view.js +24 -1
- package/dist/modes/print/print-mode.d.ts +11 -0
- package/dist/modes/print/print-mode.js +36 -1
- package/dist/modes/rpc/commands.d.ts +2 -1
- package/dist/modes/rpc/commands.js +9 -1
- package/dist/rpc.d.ts +13 -0
- package/dist/rpc.js +3 -0
- package/dist/tools/task-ctl.d.ts +2 -0
- package/dist/tools/task-ctl.js +7 -2
- package/dist/tools/task.d.ts +11 -0
- package/dist/tools/task.js +20 -2
- package/dist/tui/components/editor.d.ts +2 -0
- package/dist/tui/components/editor.js +4 -0
- package/dist/tui/components/loader.d.ts +5 -1
- package/dist/tui/components/loader.js +18 -5
- package/dist/tui/keybindings.d.ts +8 -3
- package/dist/tui/keybindings.js +8 -3
- package/docs/agents.md +52 -28
- package/docs/en/host-api.md +5 -1
- package/docs/en/providers.md +8 -4
- package/docs/en/rpc.md +19 -7
- package/docs/en/sessions.md +3 -1
- package/docs/en/tui.md +84 -66
- package/docs/host-api.md +5 -1
- package/docs/providers.md +29 -12
- package/docs/rpc.md +19 -7
- package/docs/sessions.md +2 -1
- package/docs/tui-design.md +42 -29
- package/docs/tui.md +67 -49
- package/package.json +1 -1
|
@@ -11,7 +11,10 @@ import type { ToolDefinition } from "../tools/types.js";
|
|
|
11
11
|
import type { StreamFn } from "./loop.js";
|
|
12
12
|
import type { AgentSessionOptions, SessionCore } from "./session-core.js";
|
|
13
13
|
import type { SystemPromptInput } from "./system-prompt.js";
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* 按模型的协议分派到已注册的实现(会话流的最内层)。ChatGPT 订阅模型先按本次 token 的登录方式改渠道
|
|
16
|
+
* (没写 `@渠道` 时;auth/chatgpt/follow.ts),会话中途换登录方式不用重启。
|
|
17
|
+
*/
|
|
15
18
|
export declare function providerStream(providers: ProviderRegistryApi): StreamFn;
|
|
16
19
|
export type StaticSystemInput = Omit<SystemPromptInput, "tools" | "cwd">;
|
|
17
20
|
export interface SessionSettingsDeps {
|
|
@@ -5,11 +5,16 @@
|
|
|
5
5
|
* 纯搬迁:`setModel / setThinkingLevel / setPermissionMode / setActiveTools / addTool /
|
|
6
6
|
* updateSystem / announceStart / getTools` 的行为与事件顺序不变;`AgentSessionImpl` 以同名成员转发。
|
|
7
7
|
*/
|
|
8
|
+
import { followChatGptLogin } from "../auth/chatgpt/follow.js";
|
|
8
9
|
import { AmaError } from "../errors.js";
|
|
9
10
|
import { appendModelChange, findModelOrThrow, sessionStartEvent } from "./session-sync.js";
|
|
10
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* 按模型的协议分派到已注册的实现(会话流的最内层)。ChatGPT 订阅模型先按本次 token 的登录方式改渠道
|
|
13
|
+
* (没写 `@渠道` 时;auth/chatgpt/follow.ts),会话中途换登录方式不用重启。
|
|
14
|
+
*/
|
|
11
15
|
export function providerStream(providers) {
|
|
12
|
-
return (
|
|
16
|
+
return (requested, context, streamOptions) => {
|
|
17
|
+
const model = followChatGptLogin(requested, providers.get(requested.provider), streamOptions.apiKey);
|
|
13
18
|
const api = providers.getApi(model.api);
|
|
14
19
|
if (api === undefined) {
|
|
15
20
|
throw new AmaError("provider_not_found", `no implementation registered for api ${model.api}`);
|
package/dist/agent/session.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { Agent } from "./agent.js";
|
|
|
19
19
|
import type { StreamFn } from "./loop.js";
|
|
20
20
|
import type { AgentSessionOptions, SessionCore } from "./session-core.js";
|
|
21
21
|
import { SessionCacheController } from "./session-cache.js";
|
|
22
|
+
import { type BackgroundReason } from "./subagent-registry.js";
|
|
22
23
|
import { type StaticSystemInput } from "./session-settings.js";
|
|
23
24
|
import { type RewindDraft, type SummarizeFromResult } from "./session-rewind.js";
|
|
24
25
|
import type * as CP from "../checkpoints/types.js";
|
|
@@ -78,6 +79,11 @@ export declare class AgentSessionImpl implements AgentSession, SessionCore {
|
|
|
78
79
|
followUp(text: string, options?: EnqueueOptions): Promise<"queued" | "handled">;
|
|
79
80
|
abort(): Promise<void>;
|
|
80
81
|
waitForIdle(): Promise<void>;
|
|
82
|
+
/**
|
|
83
|
+
* [W7-B1] 把阻塞中的前台子 Agent 任务转后台(不给 taskId = 全部):工具调用立即返回,任务继续,完成后
|
|
84
|
+
* 照常 `<task-notification>`。返回被转后台(或被打断等待)的 taskId。
|
|
85
|
+
*/
|
|
86
|
+
backgroundTask(taskId?: string, reason?: BackgroundReason): string[];
|
|
81
87
|
clearQueue(): {
|
|
82
88
|
steering: string[];
|
|
83
89
|
followUp: string[];
|
package/dist/agent/session.js
CHANGED
|
@@ -20,6 +20,7 @@ import { CompactionController } from "./session-compaction.js";
|
|
|
20
20
|
import { makeUserMessage, normalizeOrigin, runPrompt } from "./session-run.js";
|
|
21
21
|
import { buildSessionState, computeStats, lastAssistantText } from "./session-state.js";
|
|
22
22
|
import { DEFAULT_SUBAGENT_CONCURRENCY, SubagentPool, runSubagent } from "./session-subagent.js";
|
|
23
|
+
import { registryOf } from "./subagent-registry.js";
|
|
23
24
|
import { persistMessage, runHookWithEvents, syncSystemMessage } from "./session-sync.js";
|
|
24
25
|
import { SessionExtensions } from "./session-extensions.js";
|
|
25
26
|
import { SessionSettings, providerStream } from "./session-settings.js";
|
|
@@ -323,6 +324,13 @@ export class AgentSessionImpl {
|
|
|
323
324
|
waitForIdle() {
|
|
324
325
|
return this.cycle?.promise ?? Promise.resolve();
|
|
325
326
|
}
|
|
327
|
+
/**
|
|
328
|
+
* [W7-B1] 把阻塞中的前台子 Agent 任务转后台(不给 taskId = 全部):工具调用立即返回,任务继续,完成后
|
|
329
|
+
* 照常 `<task-notification>`。返回被转后台(或被打断等待)的 taskId。
|
|
330
|
+
*/
|
|
331
|
+
backgroundTask(taskId, reason = "host") {
|
|
332
|
+
return registryOf(this.manager.id)?.background(taskId, reason) ?? [];
|
|
333
|
+
}
|
|
326
334
|
clearQueue() {
|
|
327
335
|
const cleared = {
|
|
328
336
|
steering: this.agent.steeringQueue.clear().map(queuedText),
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 子 Agent 的后台化(docs/agents-concurrency-plan.md §2.3、§2.5–§2.6)。[W7-B1]
|
|
3
|
+
*
|
|
4
|
+
* 从 subagent-registry.ts 拆出(保持 ≤ 600 行):
|
|
5
|
+
* - `resolveTaskBackground`:`subagents.background`(auto / always / never)→ 本会话 `task` 的缺省;
|
|
6
|
+
* - 工具结果:`startedResult`(直接后台)、`backgroundedResult`(前台转后台;固定英文文案在
|
|
7
|
+
* agents/result.ts);
|
|
8
|
+
* - `foregroundWaiter`:前台任务的等待者——转后台时以固定文案先行 resolve 工具调用,并解绑父 signal;
|
|
9
|
+
* - `WaitDetach`:`task_ctl wait` 的打断信号(`background()` 触发,按 taskId 或全部);
|
|
10
|
+
* - `TaskNotifier`:完成通知的串行投递——父会话空闲后以 followUp(origin task)开一轮;用户的 steer /
|
|
11
|
+
* followUp 总在通知之前(通知只在空闲时入场),收尾阶段入队的由 W5-H2 的同周期续投接住;每个通知
|
|
12
|
+
* 各开一轮,不合并。
|
|
13
|
+
*/
|
|
14
|
+
import type { TaskRecord } from "../agents/task-record.js";
|
|
15
|
+
import type { SubagentResult } from "../tools/types.js";
|
|
16
|
+
import type { SubagentBackgroundEvent } from "./types-w5.js";
|
|
17
|
+
export type BackgroundReason = "user" | "timeout" | "host";
|
|
18
|
+
export type BackgroundSetting = "auto" | "always" | "never";
|
|
19
|
+
/** `auto`:交互 / RPC / ACP 缺省后台,`-p`(无人值守)缺省前台。 */
|
|
20
|
+
export declare function resolveTaskBackground(setting: BackgroundSetting | undefined, unattended: boolean): boolean;
|
|
21
|
+
export declare function startedResult(record: TaskRecord, outputFile?: string): SubagentResult;
|
|
22
|
+
export declare function backgroundedResult(record: TaskRecord, reason: BackgroundReason, afterMs: number, outputFile?: string): SubagentResult;
|
|
23
|
+
/** 前台任务的等待者:`resolve` 让工具调用先返回,`detachParent` 解绑父 signal 与进度回调。 */
|
|
24
|
+
export declare function foregroundWaiter(detachParent: () => void): {
|
|
25
|
+
waiter: NonNullable<TaskRecord["foregroundWaiter"]>;
|
|
26
|
+
promise: Promise<SubagentResult>;
|
|
27
|
+
};
|
|
28
|
+
/** `task_ctl wait` 的打断信号:每个 taskId 一个,`detach` 后换新。 */
|
|
29
|
+
export declare class WaitDetach {
|
|
30
|
+
private readonly controllers;
|
|
31
|
+
private readonly waiting;
|
|
32
|
+
signal(taskId: string): AbortSignal;
|
|
33
|
+
/** 登记一次正在进行的 wait;返回注销函数。 */
|
|
34
|
+
enter(taskId: string): () => void;
|
|
35
|
+
/** 有 wait 在等的 taskId。 */
|
|
36
|
+
waited(): string[];
|
|
37
|
+
/** 打断 `taskId`(缺省全部)上的 wait;返回被打断的 taskId。 */
|
|
38
|
+
detach(taskId?: string): string[];
|
|
39
|
+
}
|
|
40
|
+
export interface NotifierHost {
|
|
41
|
+
followUp?(text: string, options?: {
|
|
42
|
+
origin?: string;
|
|
43
|
+
}): Promise<unknown>;
|
|
44
|
+
waitForIdle?(): Promise<void>;
|
|
45
|
+
log(level: "debug" | "info" | "warn" | "error", message: string): void;
|
|
46
|
+
}
|
|
47
|
+
/** 完成通知的串行投递(按完成顺序;父空闲时投递,每个通知各开一轮)。 */
|
|
48
|
+
export declare class TaskNotifier {
|
|
49
|
+
private readonly host;
|
|
50
|
+
private readonly disposed;
|
|
51
|
+
private delivery;
|
|
52
|
+
constructor(host: NotifierHost, disposed: () => boolean);
|
|
53
|
+
/** 当前投递链(含已入链通知的整个回合)。 */
|
|
54
|
+
pending(): Promise<void>;
|
|
55
|
+
notify(record: TaskRecord, result: SubagentResult): void;
|
|
56
|
+
}
|
|
57
|
+
/** 等任务结束、超时或 signal 触发(后两者返回 undefined);登记进 `waits` 供 `blocking()` 查看。 */
|
|
58
|
+
export declare function waitForTask(record: TaskRecord, timeoutMs: number, signal: AbortSignal, waits: WaitDetach): Promise<SubagentResult | undefined>;
|
|
59
|
+
/** 运行中的前台任务与有 `task_ctl wait` 在等的任务。 */
|
|
60
|
+
export declare function blockingTasks(records: Iterable<TaskRecord>, waits: WaitDetach): string[];
|
|
61
|
+
export interface BackgroundContext {
|
|
62
|
+
emit(event: SubagentBackgroundEvent): void;
|
|
63
|
+
persist(record: TaskRecord): void;
|
|
64
|
+
notify(record: TaskRecord, result: SubagentResult): void;
|
|
65
|
+
/** 超时转后台的阈值(文案里的秒数)。 */
|
|
66
|
+
afterMs: number;
|
|
67
|
+
outputFile(taskId: string): string | undefined;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* 前台 → 后台(§2.5.2):只动运行中、仍在前台的任务;置后台、落快照、解绑父 signal、发事件、完成后通知、
|
|
71
|
+
* 让工具调用以固定文案返回。以 `info.status` 为准——已收尾的任务不动(与结束竞争时先到者为准)。
|
|
72
|
+
*/
|
|
73
|
+
export declare function backgroundRecords(records: readonly TaskRecord[], reason: BackgroundReason, ctx: BackgroundContext): string[];
|
|
74
|
+
/** 等到没有运行中的任务、投递链也已结束(通知回合里新起的任务继续等);会话关闭立即返回。 */
|
|
75
|
+
export declare function settleTasks(tasks: ReadonlyMap<string, TaskRecord>, notifier: TaskNotifier, disposed: () => boolean): Promise<void>;
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 子 Agent 的后台化(docs/agents-concurrency-plan.md §2.3、§2.5–§2.6)。[W7-B1]
|
|
3
|
+
*
|
|
4
|
+
* 从 subagent-registry.ts 拆出(保持 ≤ 600 行):
|
|
5
|
+
* - `resolveTaskBackground`:`subagents.background`(auto / always / never)→ 本会话 `task` 的缺省;
|
|
6
|
+
* - 工具结果:`startedResult`(直接后台)、`backgroundedResult`(前台转后台;固定英文文案在
|
|
7
|
+
* agents/result.ts);
|
|
8
|
+
* - `foregroundWaiter`:前台任务的等待者——转后台时以固定文案先行 resolve 工具调用,并解绑父 signal;
|
|
9
|
+
* - `WaitDetach`:`task_ctl wait` 的打断信号(`background()` 触发,按 taskId 或全部);
|
|
10
|
+
* - `TaskNotifier`:完成通知的串行投递——父会话空闲后以 followUp(origin task)开一轮;用户的 steer /
|
|
11
|
+
* followUp 总在通知之前(通知只在空闲时入场),收尾阶段入队的由 W5-H2 的同周期续投接住;每个通知
|
|
12
|
+
* 各开一轮,不合并。
|
|
13
|
+
*/
|
|
14
|
+
import { backgroundedText, startedText, taskNotification } from "../agents/result.js";
|
|
15
|
+
import { ZERO_USAGE } from "./loop.js";
|
|
16
|
+
/** `auto`:交互 / RPC / ACP 缺省后台,`-p`(无人值守)缺省前台。 */
|
|
17
|
+
export function resolveTaskBackground(setting, unattended) {
|
|
18
|
+
if (setting === "always")
|
|
19
|
+
return true;
|
|
20
|
+
if (setting === "never")
|
|
21
|
+
return false;
|
|
22
|
+
return !unattended;
|
|
23
|
+
}
|
|
24
|
+
function runningResult(record, text, outputFile) {
|
|
25
|
+
const result = {
|
|
26
|
+
text,
|
|
27
|
+
usage: { ...ZERO_USAGE },
|
|
28
|
+
stopReason: "stop",
|
|
29
|
+
isError: false,
|
|
30
|
+
taskId: record.info.taskId,
|
|
31
|
+
status: "running",
|
|
32
|
+
};
|
|
33
|
+
if (outputFile !== undefined)
|
|
34
|
+
result.outputFile = outputFile;
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
export function startedResult(record, outputFile) {
|
|
38
|
+
return runningResult(record, startedText(record.info.taskId, record.agent.name), outputFile);
|
|
39
|
+
}
|
|
40
|
+
export function backgroundedResult(record, reason, afterMs, outputFile) {
|
|
41
|
+
return runningResult(record, backgroundedText(reason, afterMs), outputFile);
|
|
42
|
+
}
|
|
43
|
+
/** 前台任务的等待者:`resolve` 让工具调用先返回,`detachParent` 解绑父 signal 与进度回调。 */
|
|
44
|
+
export function foregroundWaiter(detachParent) {
|
|
45
|
+
let resolve;
|
|
46
|
+
const promise = new Promise((r) => {
|
|
47
|
+
resolve = r;
|
|
48
|
+
});
|
|
49
|
+
return { waiter: { resolve, detachParent }, promise };
|
|
50
|
+
}
|
|
51
|
+
/** `task_ctl wait` 的打断信号:每个 taskId 一个,`detach` 后换新。 */
|
|
52
|
+
export class WaitDetach {
|
|
53
|
+
controllers = new Map();
|
|
54
|
+
waiting = new Map();
|
|
55
|
+
signal(taskId) {
|
|
56
|
+
let controller = this.controllers.get(taskId);
|
|
57
|
+
if (controller === undefined) {
|
|
58
|
+
controller = new AbortController();
|
|
59
|
+
this.controllers.set(taskId, controller);
|
|
60
|
+
}
|
|
61
|
+
return controller.signal;
|
|
62
|
+
}
|
|
63
|
+
/** 登记一次正在进行的 wait;返回注销函数。 */
|
|
64
|
+
enter(taskId) {
|
|
65
|
+
this.waiting.set(taskId, (this.waiting.get(taskId) ?? 0) + 1);
|
|
66
|
+
return () => {
|
|
67
|
+
const left = (this.waiting.get(taskId) ?? 1) - 1;
|
|
68
|
+
if (left <= 0)
|
|
69
|
+
this.waiting.delete(taskId);
|
|
70
|
+
else
|
|
71
|
+
this.waiting.set(taskId, left);
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/** 有 wait 在等的 taskId。 */
|
|
75
|
+
waited() {
|
|
76
|
+
return [...this.waiting.keys()];
|
|
77
|
+
}
|
|
78
|
+
/** 打断 `taskId`(缺省全部)上的 wait;返回被打断的 taskId。 */
|
|
79
|
+
detach(taskId) {
|
|
80
|
+
const ids = taskId === undefined ? this.waited() : this.waiting.has(taskId) ? [taskId] : [];
|
|
81
|
+
for (const id of taskId === undefined ? [...this.controllers.keys()] : [taskId]) {
|
|
82
|
+
this.controllers.get(id)?.abort();
|
|
83
|
+
this.controllers.delete(id);
|
|
84
|
+
}
|
|
85
|
+
return ids;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** 完成通知的串行投递(按完成顺序;父空闲时投递,每个通知各开一轮)。 */
|
|
89
|
+
export class TaskNotifier {
|
|
90
|
+
host;
|
|
91
|
+
disposed;
|
|
92
|
+
delivery = Promise.resolve();
|
|
93
|
+
constructor(host, disposed) {
|
|
94
|
+
this.host = host;
|
|
95
|
+
this.disposed = disposed;
|
|
96
|
+
}
|
|
97
|
+
/** 当前投递链(含已入链通知的整个回合)。 */
|
|
98
|
+
pending() {
|
|
99
|
+
return this.delivery;
|
|
100
|
+
}
|
|
101
|
+
notify(record, result) {
|
|
102
|
+
// 被停止的任务(task_ctl stop、会话关闭)不再通知:发起停止的一方已经知道
|
|
103
|
+
if (this.disposed() || result.status === "aborted")
|
|
104
|
+
return;
|
|
105
|
+
const info = record.info;
|
|
106
|
+
const text = taskNotification({
|
|
107
|
+
taskId: info.taskId,
|
|
108
|
+
agent: info.agent,
|
|
109
|
+
status: (result.status ?? "completed"),
|
|
110
|
+
...(info.turns === undefined ? {} : { turns: info.turns }),
|
|
111
|
+
...(info.usage === undefined ? {} : { usage: info.usage }),
|
|
112
|
+
...(info.outputFile === undefined ? {} : { outputFile: info.outputFile }),
|
|
113
|
+
report: result.text,
|
|
114
|
+
});
|
|
115
|
+
const host = this.host;
|
|
116
|
+
const followUp = host.followUp;
|
|
117
|
+
if (followUp === undefined) {
|
|
118
|
+
host.log("warn", `task ${info.taskId} finished but the session cannot be notified`);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
// 父空闲时投递(开新回合);父正忙则等这一周期结束再投——周期收尾阶段入队的 followUp 由同周期续投
|
|
122
|
+
// 接住(W5-H2)。串行投递保证按完成顺序到达、各开一轮。
|
|
123
|
+
this.delivery = this.delivery
|
|
124
|
+
.then(async () => {
|
|
125
|
+
await host.waitForIdle?.();
|
|
126
|
+
if (!this.disposed())
|
|
127
|
+
await followUp.call(host, text, { origin: "task" });
|
|
128
|
+
})
|
|
129
|
+
.catch((error) => host.log("warn", `task notification failed: ${String(error)}`));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/** 等任务结束、超时或 signal 触发(后两者返回 undefined);登记进 `waits` 供 `blocking()` 查看。 */
|
|
133
|
+
export async function waitForTask(record, timeoutMs, signal, waits) {
|
|
134
|
+
const running = record.running;
|
|
135
|
+
if (running === undefined)
|
|
136
|
+
return record.last;
|
|
137
|
+
if (signal.aborted)
|
|
138
|
+
return undefined;
|
|
139
|
+
const leave = waits.enter(record.info.taskId);
|
|
140
|
+
let timer;
|
|
141
|
+
let onAbort;
|
|
142
|
+
const stop = new Promise((resolve) => {
|
|
143
|
+
timer = setTimeout(() => resolve(undefined), timeoutMs);
|
|
144
|
+
onAbort = () => resolve(undefined);
|
|
145
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
146
|
+
});
|
|
147
|
+
try {
|
|
148
|
+
return await Promise.race([running, stop]);
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
clearTimeout(timer);
|
|
152
|
+
if (onAbort !== undefined)
|
|
153
|
+
signal.removeEventListener("abort", onAbort);
|
|
154
|
+
leave();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/** 运行中的前台任务与有 `task_ctl wait` 在等的任务。 */
|
|
158
|
+
export function blockingTasks(records, waits) {
|
|
159
|
+
const ids = [];
|
|
160
|
+
for (const r of records)
|
|
161
|
+
if (r.foregroundWaiter !== undefined && r.running !== undefined)
|
|
162
|
+
ids.push(r.info.taskId);
|
|
163
|
+
return [...new Set([...ids, ...waits.waited()])];
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* 前台 → 后台(§2.5.2):只动运行中、仍在前台的任务;置后台、落快照、解绑父 signal、发事件、完成后通知、
|
|
167
|
+
* 让工具调用以固定文案返回。以 `info.status` 为准——已收尾的任务不动(与结束竞争时先到者为准)。
|
|
168
|
+
*/
|
|
169
|
+
export function backgroundRecords(records, reason, ctx) {
|
|
170
|
+
const moved = [];
|
|
171
|
+
for (const record of records) {
|
|
172
|
+
const waiter = record.foregroundWaiter;
|
|
173
|
+
const running = record.running;
|
|
174
|
+
if (waiter === undefined || running === undefined || record.info.status !== "running")
|
|
175
|
+
continue;
|
|
176
|
+
delete record.foregroundWaiter;
|
|
177
|
+
record.info.background = true;
|
|
178
|
+
waiter.detachParent();
|
|
179
|
+
ctx.persist(record);
|
|
180
|
+
const taskId = record.info.taskId;
|
|
181
|
+
ctx.emit({
|
|
182
|
+
type: "subagent_background",
|
|
183
|
+
taskId,
|
|
184
|
+
parentToolCallId: record.parentToolCallId,
|
|
185
|
+
reason,
|
|
186
|
+
});
|
|
187
|
+
void running.then((result) => ctx.notify(record, result));
|
|
188
|
+
waiter.resolve(backgroundedResult(record, reason, ctx.afterMs, ctx.outputFile(taskId)));
|
|
189
|
+
moved.push(taskId);
|
|
190
|
+
}
|
|
191
|
+
return moved;
|
|
192
|
+
}
|
|
193
|
+
/** 等到没有运行中的任务、投递链也已结束(通知回合里新起的任务继续等);会话关闭立即返回。 */
|
|
194
|
+
export async function settleTasks(tasks, notifier, disposed) {
|
|
195
|
+
const running = () => [...tasks.values()].flatMap((r) => (r.running === undefined ? [] : [r.running]));
|
|
196
|
+
for (;;) {
|
|
197
|
+
if (disposed())
|
|
198
|
+
return;
|
|
199
|
+
const now = running();
|
|
200
|
+
if (now.length > 0) {
|
|
201
|
+
await Promise.allSettled(now);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
const delivery = notifier.pending();
|
|
205
|
+
await delivery;
|
|
206
|
+
if (delivery === notifier.pending() && running().length === 0)
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* - 事件 `subagent_start / update / end`;父会话 `custom{ama.task}` 记任务快照(带 `status`),
|
|
14
14
|
* resume 时据此重建(未完成标 `interrupted`)。
|
|
15
15
|
* - [W6-A] 子 Agent 视图的 `live()` / `message()`(实现在 subagent-direct.ts)。
|
|
16
|
+
* - [W7-B1] 缺省后台、前台转后台 `background()`、通知投递与 `settled()`(subagent-background.ts)。
|
|
16
17
|
*/
|
|
17
18
|
import type { SubagentRequest, SubagentResult, SubagentRunner, TaskInfo, TaskRegistryView } from "../tools/types.js";
|
|
18
19
|
import { AgentCatalog, type AgentModelConfig } from "../agents/catalog.js";
|
|
@@ -21,10 +22,12 @@ import { type TaskControl } from "../agents/task-control.js";
|
|
|
21
22
|
import type { AgentDefinition, AgentInfo } from "../agents/types.js";
|
|
22
23
|
import type { SessionCore } from "./session-core.js";
|
|
23
24
|
import type { SessionTaskStats } from "./types.js";
|
|
25
|
+
import { type BackgroundReason } from "./subagent-background.js";
|
|
24
26
|
export declare const DEFAULT_SUBAGENT_CONCURRENCY = 4;
|
|
25
27
|
export declare const DEFAULT_MAX_PENDING = 16;
|
|
26
28
|
export declare const DEFAULT_RETAINED = 16;
|
|
27
29
|
export { SubagentPool, TASK_CUSTOM_TYPE, type AmaRunnerSpec, type TaskHandle };
|
|
30
|
+
export type { BackgroundReason };
|
|
28
31
|
export type AmaRunnerFactory = (spec: AmaRunnerSpec) => SubagentRunner;
|
|
29
32
|
export interface SubagentEnvironment {
|
|
30
33
|
catalog: AgentCatalog;
|
|
@@ -35,6 +38,10 @@ export interface SubagentEnvironment {
|
|
|
35
38
|
/** 外部 / 宿主 runner(W5-E `ProcessRunner`、HostApi.runners);没有返回 undefined。 */
|
|
36
39
|
runners?(agent: AgentDefinition): SubagentRunner | undefined;
|
|
37
40
|
now?(): number;
|
|
41
|
+
/** [W7-B1] `task` 未指定 background 且类型也没指定时的缺省(`subagents.background` 解析后);缺省前台。 */
|
|
42
|
+
background?: boolean;
|
|
43
|
+
/** [W7-B1] 前台任务运行超过该毫秒数自动转后台(`subagents.autoBackgroundAfterMs`);0 / 不设关闭。 */
|
|
44
|
+
autoBackgroundAfterMs?: number;
|
|
38
45
|
}
|
|
39
46
|
/** 注册表需要的父会话能力(`SessionCore`;followUp 为 AgentSessionImpl 的公开方法)。 */
|
|
40
47
|
export type RegistryHost = Pick<SessionCore, "manager" | "cwd" | "emit" | "appendEntry" | "outputDir" | "log" | "options"> & {
|
|
@@ -61,7 +68,8 @@ export declare class SubagentRegistry implements TaskControl {
|
|
|
61
68
|
private readonly retained;
|
|
62
69
|
private seq;
|
|
63
70
|
private disposed;
|
|
64
|
-
private
|
|
71
|
+
private readonly notifier;
|
|
72
|
+
private readonly waits;
|
|
65
73
|
constructor(host: RegistryHost, env: SubagentEnvironment);
|
|
66
74
|
private now;
|
|
67
75
|
list(): readonly TaskInfo[];
|
|
@@ -69,8 +77,21 @@ export declare class SubagentRegistry implements TaskControl {
|
|
|
69
77
|
stats(): SessionTaskStats | undefined;
|
|
70
78
|
/** 运行中任务的已有输出(`task_ctl output`);结束后为最终文本。 */
|
|
71
79
|
output(taskId: string): string | undefined;
|
|
72
|
-
/**
|
|
73
|
-
wait(taskId: string, timeoutMs: number
|
|
80
|
+
/** 等结束;超时或 signal(缺省本任务的转后台信号,`background()` 触发)返回 undefined。[W7-B1] */
|
|
81
|
+
wait(taskId: string, timeoutMs: number, options?: {
|
|
82
|
+
signal?: AbortSignal;
|
|
83
|
+
}): Promise<SubagentResult | undefined>;
|
|
84
|
+
/** [W7-B1] `task_ctl wait` 用的打断信号(`background()` 触发后换新)。 */
|
|
85
|
+
detachSignal(taskId: string): AbortSignal;
|
|
86
|
+
/** [W7-B1] 正在阻塞父回合的任务:运行中的前台任务与有 `task_ctl wait` 在等的任务。 */
|
|
87
|
+
blocking(): string[];
|
|
88
|
+
/**
|
|
89
|
+
* [W7-B1] 前台任务转后台(§2.5,subagent-background.ts),并打断等它的 `task_ctl wait`;不给 taskId =
|
|
90
|
+
* 全部。返回被转后台或被打断等待的 taskId(不在运行时为空)。
|
|
91
|
+
*/
|
|
92
|
+
background(taskId?: string, reason?: BackgroundReason): string[];
|
|
93
|
+
/** [W7-B1] `-p` 收尾:等到没有运行中的任务、通知回合也已跑完;会话关闭立即返回。 */
|
|
94
|
+
settled(): Promise<void>;
|
|
74
95
|
/** [W6-A] 子 Agent 视图读的实时数据;未知任务 undefined。 */
|
|
75
96
|
live(taskId: string): TaskLive | undefined;
|
|
76
97
|
/** [W6-A] 人在子 Agent 视图里发的消息(subagent-direct.ts)。 */
|
|
@@ -79,13 +100,11 @@ export declare class SubagentRegistry implements TaskControl {
|
|
|
79
100
|
run(request: SubagentRequest, ama: AmaRunnerFactory): Promise<SubagentResult>;
|
|
80
101
|
private continueTask;
|
|
81
102
|
private launch;
|
|
82
|
-
private startedResult;
|
|
83
103
|
private execute;
|
|
84
104
|
private handleFor;
|
|
85
105
|
private emitStart;
|
|
86
106
|
private sink;
|
|
87
107
|
private finish;
|
|
88
|
-
private notify;
|
|
89
108
|
private outputDirectory;
|
|
90
109
|
private outputName;
|
|
91
110
|
private outputFileFor;
|