@oh-my-pi/pi-coding-agent 17.2.9 → 17.2.11
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 +83 -0
- package/dist/{CHANGELOG-8yy8h3bc.md → CHANGELOG-d9xenpn9.md} +83 -0
- package/dist/cli.js +10208 -12596
- package/dist/types/capability/mcp.d.ts +11 -0
- package/dist/types/capability/skill.d.ts +6 -0
- package/dist/types/cli/args.d.ts +1 -0
- package/dist/types/cli/command-help.d.ts +3 -0
- package/dist/types/commands/share.d.ts +25 -0
- package/dist/types/commit/agentic/index.d.ts +3 -1
- package/dist/types/commit/execute.d.ts +32 -0
- package/dist/types/commit/index.d.ts +2 -1
- package/dist/types/commit/pipeline.d.ts +7 -1
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/model-resolver.d.ts +13 -0
- package/dist/types/config/settings-schema.d.ts +1 -34
- package/dist/types/discovery/agent-plugin-format.d.ts +143 -0
- package/dist/types/discovery/agent-plugins.d.ts +1 -0
- package/dist/types/discovery/contained-path.d.ts +34 -0
- package/dist/types/discovery/index.d.ts +1 -0
- package/dist/types/extensibility/custom-commands/types.d.ts +5 -5
- package/dist/types/extensibility/custom-tools/types.d.ts +3 -3
- package/dist/types/extensibility/extensions/loader.d.ts +9 -2
- package/dist/types/extensibility/extensions/types.d.ts +45 -8
- package/dist/types/extensibility/hooks/types.d.ts +5 -5
- package/dist/types/extensibility/shared-events.d.ts +3 -2
- package/dist/types/extensibility/skills.d.ts +5 -0
- package/dist/types/index.d.ts +2 -2
- package/dist/types/main.d.ts +1 -1
- package/dist/types/mcp/transports/header-policy.d.ts +45 -0
- package/dist/types/mcp/types.d.ts +15 -0
- package/dist/types/modes/acp/acp-agent.d.ts +1 -1
- package/dist/types/modes/acp/acp-client-bridge.d.ts +1 -1
- package/dist/types/modes/acp/acp-event-mapper.d.ts +1 -1
- package/dist/types/modes/acp/acp-mode.d.ts +1 -1
- package/dist/types/modes/components/agent-hub-projection.d.ts +38 -0
- package/dist/types/modes/components/agent-hub-renderer.d.ts +41 -0
- package/dist/types/modes/components/agent-hub.d.ts +14 -5
- package/dist/types/modes/components/agent-transcript-viewer.d.ts +2 -0
- package/dist/types/modes/components/chat-transcript-builder.d.ts +2 -0
- package/dist/types/modes/components/custom-editor.d.ts +1 -2
- package/dist/types/modes/components/model-browser.d.ts +9 -1
- package/dist/types/modes/components/status-line/types.d.ts +2 -0
- package/dist/types/modes/components/tool-execution.d.ts +2 -0
- package/dist/types/modes/interactive-mode.d.ts +8 -1
- package/dist/types/modes/utils/transcript-render-helpers.d.ts +3 -2
- package/dist/types/registry/agent-registry.d.ts +47 -0
- package/dist/types/registry/persisted-agents.d.ts +3 -1
- package/dist/types/secrets/index.d.ts +23 -1
- package/dist/types/session/agent-session-events.d.ts +2 -2
- package/dist/types/session/agent-session.d.ts +4 -2
- package/dist/types/session/messages.d.ts +1 -0
- package/dist/types/session/prewalk.d.ts +3 -1
- package/dist/types/session/session-entries.d.ts +10 -0
- package/dist/types/session/session-loader.d.ts +11 -1
- package/dist/types/session/session-manager.d.ts +9 -1
- package/dist/types/session/session-tools.d.ts +18 -1
- package/dist/types/session/turn-recovery.d.ts +7 -2
- package/dist/types/slash-commands/acp-builtins.d.ts +1 -1
- package/dist/types/slash-commands/available-commands.d.ts +1 -1
- package/dist/types/task/executor.d.ts +6 -0
- package/dist/types/task/index.d.ts +4 -3
- package/dist/types/task/read-only-policy.d.ts +3 -0
- package/dist/types/task/structured-subagent.d.ts +2 -0
- package/dist/types/task/types.d.ts +4 -0
- package/dist/types/tools/bash.d.ts +3 -3
- package/dist/types/tools/browser/launch.d.ts +2 -1
- package/dist/types/tools/browser/tab-worker.d.ts +2 -2
- package/dist/types/tools/path-utils.d.ts +1 -0
- package/dist/types/tools/read.d.ts +1 -5
- package/dist/types/tools/run-scope.d.ts +27 -1
- package/dist/types/tools/shell-tokenize.d.ts +16 -0
- package/dist/types/tools/terminal-output.d.ts +1 -1
- package/dist/types/utils/turndown.d.ts +1 -1
- package/dist/types/vibe/runtime.d.ts +2 -3
- package/dist/types/web/scrapers/readthedocs.d.ts +0 -3
- package/dist/types/web/search/providers/browser-headers.d.ts +2 -3
- package/examples/custom-tools/README.md +1 -1
- package/examples/extensions/README.md +1 -1
- package/examples/extensions/api-demo.ts +5 -6
- package/examples/extensions/chalk-logger.ts +1 -1
- package/examples/extensions/hello.ts +2 -2
- package/examples/extensions/reload-runtime.ts +1 -1
- package/examples/extensions/tools.ts +2 -2
- package/examples/extensions/with-deps/index.ts +1 -1
- package/examples/sdk/06-extensions.ts +1 -1
- package/package.json +15 -31
- package/scripts/bundle-dist.ts +1 -11
- package/scripts/legacy-pi-virtual-module.ts +4 -1
- package/src/advisor/runtime.ts +54 -0
- package/src/capability/mcp.ts +11 -0
- package/src/capability/skill.ts +6 -0
- package/src/cli/agents-cli.ts +1 -1
- package/src/cli/args.ts +24 -2
- package/src/cli/auth-broker-cli.ts +1 -1
- package/src/cli/auth-gateway-cli.ts +1 -1
- package/src/cli/bench-cli.ts +1 -1
- package/src/cli/claude-trace-cli.ts +1 -1
- package/src/cli/command-help.ts +4 -0
- package/src/cli/config-cli.ts +1 -1
- package/src/cli/dry-balance-cli.ts +1 -1
- package/src/cli/file-processor.ts +1 -1
- package/src/cli/flag-tables.ts +4 -0
- package/src/cli/gallery-cli.ts +1 -1
- package/src/cli/grep-cli.ts +1 -1
- package/src/cli/grievances-cli.ts +1 -1
- package/src/cli/help-extra.ts +1 -1
- package/src/cli/models-cli.ts +1 -1
- package/src/cli/plugin-cli.ts +1 -1
- package/src/cli/read-cli.ts +1 -1
- package/src/cli/setup-cli.ts +1 -1
- package/src/cli/shell-cli.ts +1 -1
- package/src/cli/ssh-cli.ts +1 -1
- package/src/cli/stats-cli.ts +1 -1
- package/src/cli/tiny-models-cli.ts +1 -1
- package/src/cli/ttsr-cli.ts +1 -1
- package/src/cli/update-cli.ts +1 -1
- package/src/cli/usage-cli.ts +1 -1
- package/src/cli/web-search-cli.ts +1 -1
- package/src/cli/worktree-cli.ts +1 -1
- package/src/cli-commands.ts +5 -0
- package/src/commands/commit.ts +16 -3
- package/src/commands/say.ts +1 -1
- package/src/commands/share.ts +71 -0
- package/src/commands/token.ts +1 -1
- package/src/commit/agentic/agent.ts +1 -1
- package/src/commit/agentic/index.ts +39 -21
- package/src/commit/cli.ts +1 -1
- package/src/commit/execute.ts +56 -0
- package/src/commit/index.ts +2 -1
- package/src/commit/pipeline.ts +20 -7
- package/src/config/model-discovery.ts +1 -1
- package/src/config/model-registry.ts +21 -1
- package/src/config/model-resolver.ts +54 -7
- package/src/config/settings-schema.ts +2 -33
- package/src/config/settings.ts +46 -0
- package/src/discovery/agent-plugin-format.ts +551 -0
- package/src/discovery/agent-plugins.ts +341 -0
- package/src/discovery/agents.ts +4 -2
- package/src/discovery/builtin-rules/ts-no-inline-cast-access.md +5 -4
- package/src/discovery/builtin-rules/ts-no-tiny-functions.md +1 -1
- package/src/discovery/claude-plugins.ts +21 -5
- package/src/discovery/contained-path.ts +78 -0
- package/src/discovery/helpers.ts +23 -9
- package/src/discovery/index.ts +1 -0
- package/src/discovery/omp-plugins.ts +20 -7
- package/src/eval/py/runner.py +38 -1
- package/src/exec/non-interactive-env.ts +1 -0
- package/src/extensibility/custom-commands/loader.ts +4 -4
- package/src/extensibility/custom-commands/types.ts +5 -5
- package/src/extensibility/custom-tools/loader.ts +4 -4
- package/src/extensibility/custom-tools/types.ts +3 -3
- package/src/extensibility/extensions/loader.ts +85 -20
- package/src/extensibility/extensions/runner.ts +6 -0
- package/src/extensibility/extensions/types.ts +51 -8
- package/src/extensibility/hooks/loader.ts +4 -5
- package/src/extensibility/hooks/types.ts +5 -5
- package/src/extensibility/plugins/legacy-pi-compat.ts +53 -0
- package/src/extensibility/plugins/marketplace/manager.ts +2 -1
- package/src/extensibility/shared-events.ts +3 -2
- package/src/extensibility/skills.ts +9 -0
- package/src/index.ts +2 -2
- package/src/internal-urls/memory-protocol.ts +5 -1
- package/src/internal-urls/skill-protocol.ts +14 -0
- package/src/internal-urls/vault-protocol.ts +2 -2
- package/src/launch/client.ts +11 -1
- package/src/launch/protocol.ts +7 -2
- package/src/launch/terminal-output.ts +1 -1
- package/src/main.ts +88 -23
- package/src/markit/converters/docx.ts +1 -1
- package/src/markit/converters/epub.ts +1 -1
- package/src/markit/converters/pptx.ts +1 -1
- package/src/markit/converters/xlsx.ts +1 -1
- package/src/mcp/config.ts +3 -0
- package/src/mcp/manager.ts +12 -9
- package/src/mcp/transports/header-policy.ts +95 -0
- package/src/mcp/transports/http.ts +35 -52
- package/src/mcp/transports/sse.ts +20 -27
- package/src/mcp/types.ts +15 -0
- package/src/modes/acp/acp-agent.ts +20 -9
- package/src/modes/acp/acp-client-bridge.ts +1 -1
- package/src/modes/acp/acp-event-mapper.ts +1 -1
- package/src/modes/acp/acp-mode.ts +1 -1
- package/src/modes/components/agent-dashboard.ts +2 -0
- package/src/modes/components/agent-hub-projection.ts +248 -0
- package/src/modes/components/agent-hub-renderer.ts +194 -0
- package/src/modes/components/agent-hub.ts +674 -207
- package/src/modes/components/agent-transcript-viewer.ts +3 -0
- package/src/modes/components/assistant-message.ts +1 -1
- package/src/modes/components/chat-transcript-builder.ts +3 -0
- package/src/modes/components/custom-editor.ts +0 -9
- package/src/modes/components/extensions/inspector-panel.ts +11 -6
- package/src/modes/components/footer.ts +1 -3
- package/src/modes/components/model-browser.ts +11 -3
- package/src/modes/components/model-hub.ts +4 -1
- package/src/modes/components/status-line/component.ts +1 -0
- package/src/modes/components/status-line/segments.ts +10 -7
- package/src/modes/components/status-line/types.ts +2 -0
- package/src/modes/components/tool-execution.ts +13 -16
- package/src/modes/components/tree-selector.ts +62 -3
- package/src/modes/controllers/command-controller.ts +4 -1
- package/src/modes/controllers/event-controller.ts +44 -20
- package/src/modes/controllers/extension-ui-controller.ts +2 -2
- package/src/modes/controllers/input-controller.ts +20 -2
- package/src/modes/controllers/mcp-command-controller.ts +81 -20
- package/src/modes/controllers/selector-controller.ts +41 -49
- package/src/modes/controllers/streaming-reveal.ts +1 -1
- package/src/modes/interactive-mode.ts +13 -2
- package/src/modes/rpc/rpc-mode.ts +2 -2
- package/src/modes/runtime-init.ts +1 -1
- package/src/modes/theme/theme.ts +2 -2
- package/src/modes/utils/transcript-render-helpers.ts +4 -2
- package/src/modes/utils/ui-helpers.ts +1 -0
- package/src/prompts/tools/computer.md +1 -1
- package/src/registry/agent-lifecycle.ts +14 -7
- package/src/registry/agent-registry.ts +65 -2
- package/src/registry/persisted-agents.ts +341 -16
- package/src/sdk.ts +6 -49
- package/src/secrets/index.ts +52 -1
- package/src/session/agent-session-events.ts +2 -2
- package/src/session/agent-session.ts +54 -16
- package/src/session/messages.ts +1 -0
- package/src/session/prewalk.ts +57 -17
- package/src/session/session-context.ts +3 -5
- package/src/session/session-entries.ts +10 -0
- package/src/session/session-handoff.ts +5 -1
- package/src/session/session-listing.ts +1 -1
- package/src/session/session-loader.ts +74 -8
- package/src/session/session-manager.ts +22 -2
- package/src/session/session-paths.ts +57 -9
- package/src/session/session-tools.ts +65 -4
- package/src/session/todo-tracker.ts +11 -1
- package/src/session/turn-recovery.ts +96 -74
- package/src/slash-commands/acp-builtins.ts +1 -1
- package/src/slash-commands/available-commands.ts +1 -1
- package/src/slash-commands/builtin-registry.ts +12 -8
- package/src/task/executor.ts +70 -20
- package/src/task/index.ts +30 -34
- package/src/task/isolation-runner.ts +24 -11
- package/src/task/persisted-revive.ts +12 -5
- package/src/task/read-only-policy.ts +27 -0
- package/src/task/structured-subagent.ts +14 -4
- package/src/task/types.ts +4 -0
- package/src/tools/auto-generated-guard.ts +1 -1
- package/src/tools/bash-interactive.ts +4 -4
- package/src/tools/bash-skill-urls.ts +15 -0
- package/src/tools/bash.ts +16 -17
- package/src/tools/browser/cmux/cmux-tab.ts +66 -18
- package/src/tools/browser/launch.ts +41 -12
- package/src/tools/browser/readable.ts +9 -9
- package/src/tools/browser/tab-supervisor.ts +12 -3
- package/src/tools/browser/tab-worker-entry.ts +1 -1
- package/src/tools/browser/tab-worker.ts +101 -12
- package/src/tools/debug.ts +1 -1
- package/src/tools/fetch.ts +1 -1
- package/src/tools/jtd-to-json-schema.ts +123 -21
- package/src/tools/path-utils.ts +9 -2
- package/src/tools/read.ts +21 -9
- package/src/tools/run-scope.ts +290 -4
- package/src/tools/shell-tokenize.ts +98 -0
- package/src/tools/terminal-output.ts +1 -1
- package/src/tools/todo.ts +1 -1
- package/src/utils/clipboard.ts +10 -2
- package/src/utils/external-editor.ts +4 -2
- package/src/utils/jj.ts +1 -1
- package/src/utils/turndown.ts +1 -2
- package/src/vibe/runtime.ts +5 -5
- package/src/web/scrapers/arxiv.ts +1 -1
- package/src/web/scrapers/go-pkg.ts +1 -1
- package/src/web/scrapers/iacr.ts +1 -1
- package/src/web/scrapers/readthedocs.ts +2 -1
- package/src/web/scrapers/twitter.ts +2 -2
- package/src/web/scrapers/types.ts +1 -1
- package/src/web/scrapers/wikipedia.ts +1 -1
- package/src/web/search/providers/browser-headers.ts +12 -39
- package/src/web/search/providers/codex.ts +3 -26
- package/src/web/search/providers/ecosia.ts +1 -1
- package/src/web/search/providers/exa.ts +1 -1
- package/src/web/search/providers/google.ts +1 -1
- package/src/web/search/providers/mojeek.ts +1 -1
- package/src/web/search/providers/startpage.ts +1 -1
- package/src/markit/converters/mammoth.d.ts +0 -24
|
@@ -89,6 +89,66 @@ function raceAbortSignal<T>(promise: Promise<T>, signal: AbortSignal, createErro
|
|
|
89
89
|
});
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
type ActiveMCPOAuthFlow = {
|
|
93
|
+
cancel: (reason: string) => void;
|
|
94
|
+
completion: Promise<void>;
|
|
95
|
+
complete: () => void;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
type MCPOAuthFlowCoordinator = {
|
|
99
|
+
active?: ActiveMCPOAuthFlow;
|
|
100
|
+
transition: Promise<void>;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const mcpOAuthFlowCoordinators = new WeakMap<object, MCPOAuthFlowCoordinator>();
|
|
104
|
+
const MCP_OAUTH_SUPERSEDED_REASON = "MCP OAuth flow superseded by a new login";
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Serialize MCP OAuth ownership across slash-command controller instances.
|
|
108
|
+
* Interactive mode creates a new controller for every command, while the
|
|
109
|
+
* manual-input manager remains stable for the session and is therefore the
|
|
110
|
+
* lifecycle key.
|
|
111
|
+
*/
|
|
112
|
+
async function claimMCPOAuthFlow(owner: object, cancel: (reason: string) => void): Promise<{ release: () => void }> {
|
|
113
|
+
let coordinator = mcpOAuthFlowCoordinators.get(owner);
|
|
114
|
+
if (!coordinator) {
|
|
115
|
+
coordinator = { transition: Promise.resolve() };
|
|
116
|
+
mcpOAuthFlowCoordinators.set(owner, coordinator);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const precedingTransition = coordinator.transition;
|
|
120
|
+
const transition = Promise.withResolvers<void>();
|
|
121
|
+
coordinator.transition = transition.promise;
|
|
122
|
+
await precedingTransition;
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
const active = coordinator.active;
|
|
126
|
+
if (active) {
|
|
127
|
+
active.cancel(MCP_OAUTH_SUPERSEDED_REASON);
|
|
128
|
+
await active.completion;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const completion = Promise.withResolvers<void>();
|
|
132
|
+
const flow: ActiveMCPOAuthFlow = {
|
|
133
|
+
cancel,
|
|
134
|
+
completion: completion.promise,
|
|
135
|
+
complete: () => completion.resolve(),
|
|
136
|
+
};
|
|
137
|
+
coordinator.active = flow;
|
|
138
|
+
let released = false;
|
|
139
|
+
return {
|
|
140
|
+
release: () => {
|
|
141
|
+
if (released) return;
|
|
142
|
+
released = true;
|
|
143
|
+
if (coordinator.active === flow) coordinator.active = undefined;
|
|
144
|
+
flow.complete();
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
} finally {
|
|
148
|
+
transition.resolve();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
92
152
|
/**
|
|
93
153
|
* Minimum column budget for URL wrapping. Below this the terminal is
|
|
94
154
|
* effectively unusable, but we still emit chunks so no character is silently
|
|
@@ -782,28 +842,23 @@ export class MCPCommandController {
|
|
|
782
842
|
const resolvedClientSecret = clientSecret.trim() || undefined;
|
|
783
843
|
|
|
784
844
|
const manualInput = this.ctx.oauthManualInput;
|
|
785
|
-
if (manualInput.hasPending()) {
|
|
786
|
-
const pendingProvider = manualInput.pendingProviderId ?? "another provider";
|
|
787
|
-
throw new Error(
|
|
788
|
-
`OAuth login already in progress for ${pendingProvider}. Complete or cancel it before starting MCP OAuth.`,
|
|
789
|
-
);
|
|
790
|
-
}
|
|
791
845
|
let manualInputClaim: { promise: Promise<string>; clear: (reason?: string) => void } | undefined;
|
|
792
846
|
const oauthTimeout = new AbortController();
|
|
793
|
-
//
|
|
794
|
-
// its own reason and leaves this flag false so the
|
|
795
|
-
//
|
|
796
|
-
let
|
|
797
|
-
const
|
|
798
|
-
|
|
847
|
+
// Esc, external aborts, and a replacement MCP flow route through here;
|
|
848
|
+
// the timeout path sets its own reason and leaves this flag false so the
|
|
849
|
+
// catch can distinguish cancellation (status) from deadline failure.
|
|
850
|
+
let cancellationRequested = false;
|
|
851
|
+
const requestCancellation = (reason: string): void => {
|
|
852
|
+
cancellationRequested = true;
|
|
799
853
|
if (!oauthTimeout.signal.aborted) oauthTimeout.abort(reason);
|
|
800
854
|
};
|
|
855
|
+
const flowClaim = await claimMCPOAuthFlow(manualInput, requestCancellation);
|
|
801
856
|
const originalOnEscape = this.ctx.editor.onEscape;
|
|
802
|
-
this.ctx.editor.onEscape = () =>
|
|
857
|
+
this.ctx.editor.onEscape = () => requestCancellation(MCP_OAUTH_USER_CANCEL_REASON);
|
|
803
858
|
const externalSignal = opts?.abortSignal;
|
|
804
859
|
const onExternalAbort = (): void => {
|
|
805
860
|
const reason = externalSignal?.reason;
|
|
806
|
-
|
|
861
|
+
requestCancellation(typeof reason === "string" ? reason : MCP_OAUTH_USER_CANCEL_REASON);
|
|
807
862
|
};
|
|
808
863
|
if (externalSignal?.aborted) {
|
|
809
864
|
onExternalAbort();
|
|
@@ -811,6 +866,12 @@ export class MCPCommandController {
|
|
|
811
866
|
externalSignal?.addEventListener("abort", onExternalAbort, { once: true });
|
|
812
867
|
}
|
|
813
868
|
try {
|
|
869
|
+
if (manualInput.hasPending()) {
|
|
870
|
+
const pendingProvider = manualInput.pendingProviderId ?? "another provider";
|
|
871
|
+
throw new Error(
|
|
872
|
+
`OAuth login already in progress for ${pendingProvider}. Complete or cancel it before starting MCP OAuth.`,
|
|
873
|
+
);
|
|
874
|
+
}
|
|
814
875
|
// Create OAuth flow
|
|
815
876
|
const flow = new MCPOAuthFlow(
|
|
816
877
|
{
|
|
@@ -887,7 +948,7 @@ export class MCPCommandController {
|
|
|
887
948
|
|
|
888
949
|
const createAbortError = (): Error => {
|
|
889
950
|
const reason = String(oauthTimeout.signal.reason ?? "MCP OAuth flow aborted");
|
|
890
|
-
return
|
|
951
|
+
return cancellationRequested ? new MCPOAuthCancelledError() : new Error(reason);
|
|
891
952
|
};
|
|
892
953
|
if (oauthTimeout.signal.aborted) throw createAbortError();
|
|
893
954
|
|
|
@@ -934,11 +995,10 @@ export class MCPCommandController {
|
|
|
934
995
|
resource: flow.resource,
|
|
935
996
|
};
|
|
936
997
|
} catch (error) {
|
|
937
|
-
//
|
|
938
|
-
//
|
|
939
|
-
//
|
|
940
|
-
|
|
941
|
-
if (userCancelled) {
|
|
998
|
+
// Esc, an external abort, or a newer MCP flow are neutral
|
|
999
|
+
// cancellations. The timeout path also aborts the controller but does
|
|
1000
|
+
// not set this flag, so it remains a surfaced error.
|
|
1001
|
+
if (cancellationRequested) {
|
|
942
1002
|
throw new MCPOAuthCancelledError();
|
|
943
1003
|
}
|
|
944
1004
|
|
|
@@ -960,6 +1020,7 @@ export class MCPCommandController {
|
|
|
960
1020
|
this.ctx.editor.onEscape = originalOnEscape;
|
|
961
1021
|
externalSignal?.removeEventListener("abort", onExternalAbort);
|
|
962
1022
|
manualInputClaim?.clear("Manual MCP OAuth input cleared");
|
|
1023
|
+
flowClaim.release();
|
|
963
1024
|
}
|
|
964
1025
|
}
|
|
965
1026
|
|
|
@@ -110,6 +110,23 @@ const MANUAL_LOGIN_PROMPT = "Paste the authorization code (or full redirect URL)
|
|
|
110
110
|
|
|
111
111
|
export class SelectorController {
|
|
112
112
|
constructor(private ctx: InteractiveModeContext) {}
|
|
113
|
+
/**
|
|
114
|
+
* Mount a primary fullscreen menu through the one polished modal path shared
|
|
115
|
+
* by Settings, Model Hub, and Agent Hub.
|
|
116
|
+
*/
|
|
117
|
+
#showFullscreenMenu(component: Component): OverlayHandle {
|
|
118
|
+
const handle = this.ctx.ui.showOverlay(component, {
|
|
119
|
+
anchor: "bottom-center",
|
|
120
|
+
width: "100%",
|
|
121
|
+
maxHeight: "100%",
|
|
122
|
+
margin: 0,
|
|
123
|
+
fullscreen: true,
|
|
124
|
+
});
|
|
125
|
+
this.ctx.ui.setFocus(component);
|
|
126
|
+
this.ctx.ui.requestRender();
|
|
127
|
+
return handle;
|
|
128
|
+
}
|
|
129
|
+
|
|
113
130
|
#defaultRoleMutationTail = Promise.resolve();
|
|
114
131
|
|
|
115
132
|
async #acquireDefaultRoleMutation(): Promise<() => void> {
|
|
@@ -242,15 +259,7 @@ export class SelectorController {
|
|
|
242
259
|
},
|
|
243
260
|
},
|
|
244
261
|
);
|
|
245
|
-
overlayHandle = this
|
|
246
|
-
anchor: "bottom-center",
|
|
247
|
-
width: "100%",
|
|
248
|
-
maxHeight: "100%",
|
|
249
|
-
margin: 0,
|
|
250
|
-
fullscreen: true,
|
|
251
|
-
});
|
|
252
|
-
this.ctx.ui.setFocus(selector);
|
|
253
|
-
this.ctx.ui.requestRender();
|
|
262
|
+
overlayHandle = this.#showFullscreenMenu(selector);
|
|
254
263
|
});
|
|
255
264
|
}
|
|
256
265
|
|
|
@@ -1023,15 +1032,7 @@ export class SelectorController {
|
|
|
1023
1032
|
initialProviderId: hubOptions.initialProviderId,
|
|
1024
1033
|
},
|
|
1025
1034
|
);
|
|
1026
|
-
overlayHandle = this
|
|
1027
|
-
anchor: "bottom-center",
|
|
1028
|
-
width: "100%",
|
|
1029
|
-
maxHeight: "100%",
|
|
1030
|
-
margin: 0,
|
|
1031
|
-
fullscreen: true,
|
|
1032
|
-
});
|
|
1033
|
-
this.ctx.ui.setFocus(hub);
|
|
1034
|
-
this.ctx.ui.requestRender();
|
|
1035
|
+
overlayHandle = this.#showFullscreenMenu(hub);
|
|
1035
1036
|
}
|
|
1036
1037
|
|
|
1037
1038
|
/** /login round-trip for a locked provider; reopen the hub on that provider only after a successful login. */
|
|
@@ -2000,27 +2001,23 @@ export class SelectorController {
|
|
|
2000
2001
|
...this.ctx.keybindings.getKeys("app.agents.hub"),
|
|
2001
2002
|
...this.ctx.keybindings.getKeys("app.session.observe"),
|
|
2002
2003
|
];
|
|
2003
|
-
let
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
// every other selector (model, session, tree, the `ask` tool) uses —
|
|
2007
|
-
// rather than a floating overlay. A non-fullscreen overlay composited over
|
|
2008
|
-
// a live transcript strands a stale copy in native scrollback every time a
|
|
2009
|
-
// running subagent's progress grows the frame and scrolls the window; the
|
|
2010
|
-
// hub is opened mid-run, so those copies stacked into a wall of duplicate
|
|
2011
|
-
// "Agent Hub" frames bleeding the task tree behind them. As an editor-slot
|
|
2012
|
-
// component it rides the normal append-only commit path: the transcript
|
|
2013
|
-
// commits above it exactly once and the hub repaints in place.
|
|
2004
|
+
let overlayHandle: OverlayHandle | undefined;
|
|
2005
|
+
let closed = false;
|
|
2006
|
+
|
|
2014
2007
|
const done = () => {
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2008
|
+
if (closed) return;
|
|
2009
|
+
closed = true;
|
|
2010
|
+
hub.dispose();
|
|
2011
|
+
overlayHandle?.hide();
|
|
2012
|
+
// A gated empty Hub may never have been mounted. Restoring editor
|
|
2013
|
+
// focus in that case would steal focus from a menu opened meanwhile.
|
|
2014
|
+
if (overlayHandle) this.focusActiveEditorArea();
|
|
2019
2015
|
this.ctx.ui.requestRender();
|
|
2020
2016
|
};
|
|
2021
2017
|
|
|
2022
|
-
hub = new AgentHubOverlayComponent({
|
|
2018
|
+
const hub = new AgentHubOverlayComponent({
|
|
2023
2019
|
observers,
|
|
2020
|
+
settings: this.ctx.settings,
|
|
2024
2021
|
hubKeys,
|
|
2025
2022
|
expandKeys: this.ctx.keybindings.getKeys("app.tools.expand"),
|
|
2026
2023
|
onDone: done,
|
|
@@ -2029,6 +2026,7 @@ export class SelectorController {
|
|
|
2029
2026
|
remote: this.ctx.collabGuest?.hubRemote,
|
|
2030
2027
|
ui: this.ctx.ui,
|
|
2031
2028
|
getTool: name => this.ctx.session.getToolByName(name),
|
|
2029
|
+
isBuiltInTool: name => this.ctx.session.hasBuiltInTool(name),
|
|
2032
2030
|
getMessageRenderer: type => this.ctx.session.extensionRunner?.getMessageRenderer(type),
|
|
2033
2031
|
cwd: this.ctx.sessionManager.getCwd(),
|
|
2034
2032
|
hideThinkingBlock: () => this.ctx.effectiveHideThinkingBlock,
|
|
@@ -2038,30 +2036,24 @@ export class SelectorController {
|
|
|
2038
2036
|
});
|
|
2039
2037
|
|
|
2040
2038
|
const showReadyHub = () => {
|
|
2041
|
-
|
|
2042
|
-
//
|
|
2043
|
-
//
|
|
2044
|
-
// hub instead of treating the initial empty table as authoritative.
|
|
2039
|
+
if (closed) return;
|
|
2040
|
+
// The double-← gesture stays inert when neither live nor persisted
|
|
2041
|
+
// subagents are available, so wait for discovery before making the gate.
|
|
2045
2042
|
if (options?.requireContent && hub.isEmpty) {
|
|
2046
|
-
|
|
2043
|
+
done();
|
|
2047
2044
|
return;
|
|
2048
2045
|
}
|
|
2049
2046
|
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
this.ctx.ui.setFocus(hub);
|
|
2053
|
-
// When the hub was raised by the editor's double-← gesture, prime its own
|
|
2054
|
-
// close detector so the *next* single ← dismisses it — the two taps that
|
|
2055
|
-
// opened it were consumed by the editor's detector (issue #4780).
|
|
2047
|
+
// Prime the detector before the first frame when the editor's double-←
|
|
2048
|
+
// gesture opened the hub, so the next single ← dismisses it.
|
|
2056
2049
|
if (options?.armCloseTap) hub.armCloseTap();
|
|
2057
|
-
this
|
|
2050
|
+
overlayHandle = this.#showFullscreenMenu(hub);
|
|
2058
2051
|
};
|
|
2059
2052
|
|
|
2060
2053
|
if (options?.requireContent && hub.isEmpty) {
|
|
2061
2054
|
void hub.persistedSubagentsReady.then(showReadyHub);
|
|
2062
|
-
|
|
2055
|
+
} else {
|
|
2056
|
+
showReadyHub();
|
|
2063
2057
|
}
|
|
2064
|
-
|
|
2065
|
-
showReadyHub();
|
|
2066
2058
|
}
|
|
2067
2059
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AssistantMessage } from "@oh-my-pi/pi-ai";
|
|
2
2
|
import { type Component, getSegmenter } from "@oh-my-pi/pi-tui";
|
|
3
|
-
import { LRUCache } from "
|
|
3
|
+
import { LRUCache } from "@oh-my-pi/pi-utils/lru";
|
|
4
4
|
import { formatThinkingForDisplay, hasDisplayableThinking } from "../../utils/thinking-display";
|
|
5
5
|
import type { AssistantMessageComponent } from "../components/assistant-message";
|
|
6
6
|
|
|
@@ -52,7 +52,7 @@ import {
|
|
|
52
52
|
prompt,
|
|
53
53
|
setProjectDir,
|
|
54
54
|
} from "@oh-my-pi/pi-utils";
|
|
55
|
-
import chalk from "chalk";
|
|
55
|
+
import chalk from "@oh-my-pi/pi-utils/chalk";
|
|
56
56
|
import { reset as resetCapabilities } from "../capability";
|
|
57
57
|
import type { CollabGuestLink } from "../collab/guest";
|
|
58
58
|
import type { CollabHost } from "../collab/host";
|
|
@@ -4141,7 +4141,14 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
4141
4141
|
this.ui.requestRender();
|
|
4142
4142
|
}
|
|
4143
4143
|
|
|
4144
|
-
/**
|
|
4144
|
+
/**
|
|
4145
|
+
* Defer transcript command panels while the agent is streaming, then mount
|
|
4146
|
+
* them at the next settle, terminal or not. A non-terminal settle is only a
|
|
4147
|
+
* scheduling pause, so resumed streaming can still land below a panel
|
|
4148
|
+
* flushed there. That is preferred over leaving it queued behind a command
|
|
4149
|
+
* the user runs during the pause, which mounts immediately and would put the
|
|
4150
|
+
* older panel out of order.
|
|
4151
|
+
*/
|
|
4145
4152
|
presentCommandOutput(content: Component | readonly Component[]): void {
|
|
4146
4153
|
if (!this.session.isStreaming) {
|
|
4147
4154
|
this.present(content);
|
|
@@ -4154,6 +4161,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
4154
4161
|
this.#pendingCommandOutputSessionId = sessionId;
|
|
4155
4162
|
const items = Array.isArray(content) ? content : [content as Component];
|
|
4156
4163
|
this.#pendingCommandOutput.push(...items);
|
|
4164
|
+
// No feedback here on purpose: mounting anything into the transcript
|
|
4165
|
+
// mid-turn (even a status line) re-renders rows below the growing live
|
|
4166
|
+
// block and duplicates them in native scrollback — the exact regression
|
|
4167
|
+
// issues #4806/#6767 pin. The queue flushes at the next settle.
|
|
4157
4168
|
}
|
|
4158
4169
|
|
|
4159
4170
|
/** Mount every command panel queued for the current session while the agent was streaming. */
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { once } from "node:events";
|
|
14
14
|
import { getOAuthProviders } from "@oh-my-pi/pi-ai/oauth";
|
|
15
|
-
import {
|
|
15
|
+
import { toolWireSchema } from "@oh-my-pi/pi-ai/utils/schema";
|
|
16
16
|
import { $env, isRecord, readLines, Snowflake } from "@oh-my-pi/pi-utils";
|
|
17
17
|
import { reset as resetCapabilities } from "../../capability";
|
|
18
18
|
import { clearPluginRootsAndCaches, resolveActiveProjectRegistryPath } from "../../discovery/helpers";
|
|
@@ -1095,7 +1095,7 @@ export async function runRpcMode(
|
|
|
1095
1095
|
dumpTools: session.agent.state.tools.map(tool => ({
|
|
1096
1096
|
name: tool.name,
|
|
1097
1097
|
description: tool.description,
|
|
1098
|
-
parameters:
|
|
1098
|
+
parameters: toolWireSchema(tool),
|
|
1099
1099
|
examples: tool.examples,
|
|
1100
1100
|
})),
|
|
1101
1101
|
contextUsage: session.getContextUsage(),
|
|
@@ -84,7 +84,7 @@ export async function initializeExtensions(session: AgentSession, options: Initi
|
|
|
84
84
|
session.sessionManager.appendLabelChange(targetId, label);
|
|
85
85
|
},
|
|
86
86
|
getActiveTools: () => session.getEnabledToolNames(),
|
|
87
|
-
getAllTools: () => session.
|
|
87
|
+
getAllTools: () => session.getAllToolInfos(),
|
|
88
88
|
setActiveTools: (toolNames: string[]) => session.setActiveToolsByName(toolNames),
|
|
89
89
|
getCommands: () => getSessionSlashCommands(session),
|
|
90
90
|
setModel: model => runExtensionSetModel(session, model),
|
package/src/modes/theme/theme.ts
CHANGED
|
@@ -20,8 +20,8 @@ import type {
|
|
|
20
20
|
TerminalAppearance,
|
|
21
21
|
} from "@oh-my-pi/pi-tui";
|
|
22
22
|
import { adjustHsv, colorLuma, getCustomThemesDir, isEnoent, logger, relativeLuminance } from "@oh-my-pi/pi-utils";
|
|
23
|
-
import chalk from "chalk";
|
|
24
|
-
import { LRUCache } from "
|
|
23
|
+
import chalk from "@oh-my-pi/pi-utils/chalk";
|
|
24
|
+
import { LRUCache } from "@oh-my-pi/pi-utils/lru";
|
|
25
25
|
// Embed theme JSON files at build time
|
|
26
26
|
import darkThemeJson from "./dark.json" with { type: "json" };
|
|
27
27
|
import { defaultThemes } from "./defaults";
|
|
@@ -211,13 +211,15 @@ function sanitizeRecoveredRetryNote(note: string): string {
|
|
|
211
211
|
|
|
212
212
|
/**
|
|
213
213
|
* Resolve the turn-ending assistant error presentation, if any.
|
|
214
|
-
* Silent and user-interrupt aborts yield no label. Recovered
|
|
215
|
-
*
|
|
214
|
+
* Silent and user-interrupt aborts yield no label. Recovered retry attempts
|
|
215
|
+
* render a compact note; attempts superseded by an exhausted budget are hidden
|
|
216
|
+
* while the final terminal error keeps its full presentation.
|
|
216
217
|
*/
|
|
217
218
|
export function resolveAssistantErrorPresentation(
|
|
218
219
|
message: AssistantAgentMessage,
|
|
219
220
|
retryAttempt = 0,
|
|
220
221
|
): AssistantErrorPresentation {
|
|
222
|
+
if (message.retryRecovery?.status === "superseded") return { kind: "none" };
|
|
221
223
|
if (message.retryRecovery?.status === "recovered") {
|
|
222
224
|
return {
|
|
223
225
|
kind: "compact-recovered",
|
|
@@ -500,6 +500,7 @@ export class UiHelpers {
|
|
|
500
500
|
content.name,
|
|
501
501
|
renderArgs,
|
|
502
502
|
{
|
|
503
|
+
useBuiltInRenderer: this.ctx.viewSession.hasBuiltInTool(content.name),
|
|
503
504
|
snapshots: getFileSnapshotStore(this.ctx.viewSession),
|
|
504
505
|
clipboard: getEditClipboard(this.ctx.viewSession),
|
|
505
506
|
showImages: settings.get("terminal.showImages"),
|
|
@@ -16,7 +16,7 @@ Controls the host desktop with a JS script: windows, screenshots, native input,
|
|
|
16
16
|
- Pointer `x,y` are pixels in the MOST RECENT screenshot of the SAME target (window or desktop). No screenshot of that target yet → coordinate input throws. AX coordinates (`.bounds()`, `elementAt`) are global desktop coords — two spaces, both converted automatically; never mix them.
|
|
17
17
|
- Each `.ax()` of a window starts a new ref generation; refs from the current and previous snapshot stay valid, older ones throw StaleRef — re-snapshot, don't guess.
|
|
18
18
|
- Input defaults to `delivery: "background"` — delivered to the target window without touching the user's focus, pointer, or window order. On macOS, keyboard input to an app with multiple windows throws `BackgroundUnavailable` because the OS accepts only a process id and could send keys to a different window; retry with `delivery: "foreground"` (briefly activates the target, acts, restores focus) or act through AX instead. Targets whose input stack drops other background events also throw `BackgroundUnavailable` naming the window class and event kind. Never assume a background action landed because no error was displayed — errors are how this surface reports failure.
|
|
19
|
-
- Wayland only:
|
|
19
|
+
- Wayland only: per-window native input and `raise()` are unavailable; use AX actions, or desktop input after focusing the target yourself.
|
|
20
20
|
- `read_only: true` for pure inspection — input and mutation throw, approval is lighter.
|
|
21
21
|
- Screenshots auto-display to you and save full-res to a temp path; pass `{silent: true}` in loops.
|
|
22
22
|
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* a superseded revive) can never clobber a newer same-id ref.
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
+
import * as fs from "node:fs/promises";
|
|
23
24
|
import { logger, untilAborted } from "@oh-my-pi/pi-utils";
|
|
24
25
|
import type { AgentSession } from "../session/agent-session";
|
|
25
26
|
import { trackLateCleanup } from "../utils/late-cleanup";
|
|
@@ -27,6 +28,7 @@ import {
|
|
|
27
28
|
type AgentRef,
|
|
28
29
|
type AgentRefExpectation,
|
|
29
30
|
AgentRegistry,
|
|
31
|
+
getAgentTombstonePath,
|
|
30
32
|
MAIN_AGENT_ID,
|
|
31
33
|
type RegistryEvent,
|
|
32
34
|
} from "./agent-registry";
|
|
@@ -35,6 +37,14 @@ export type AgentReviver = (expected: AgentRef) => Promise<AgentSession>;
|
|
|
35
37
|
|
|
36
38
|
const AGENT_RELEASE_GRACE_MS = 5000;
|
|
37
39
|
|
|
40
|
+
async function persistAgentTombstone(sessionFile: string): Promise<void> {
|
|
41
|
+
try {
|
|
42
|
+
await fs.writeFile(getAgentTombstonePath(sessionFile), "", { encoding: "utf8", flag: "wx", mode: 0o600 });
|
|
43
|
+
} catch (error) {
|
|
44
|
+
if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
38
48
|
/**
|
|
39
49
|
* Builds a reviver for a `parked` ref restored from disk (Agent Hub scan,
|
|
40
50
|
* collab mirror, resumed process) that carries a sessionFile but no in-memory
|
|
@@ -382,13 +392,10 @@ export class AgentLifecycleManager {
|
|
|
382
392
|
}
|
|
383
393
|
|
|
384
394
|
if (options?.tombstone) {
|
|
385
|
-
//
|
|
386
|
-
//
|
|
387
|
-
//
|
|
388
|
-
|
|
389
|
-
// createAgentSession's dispose wrapper (unregisterUnlessParked) preserve
|
|
390
|
-
// the ref instead of removing it, so a later persisted-subagent rescan
|
|
391
|
-
// skips it. The transcript file is left intact (history://<id>).
|
|
395
|
+
// Persist the terminal decision before detaching the session. The
|
|
396
|
+
// sidecar prevents a later discovery pass from reviving this transcript
|
|
397
|
+
// as a fresh parked ref.
|
|
398
|
+
if (ref.sessionFile) await persistAgentTombstone(ref.sessionFile);
|
|
392
399
|
this.#registry.setStatus(id, "aborted", ref);
|
|
393
400
|
}
|
|
394
401
|
const live = this.#registry.get(id) === ref ? ref.session : null;
|
|
@@ -14,6 +14,13 @@ import { oneLineLabel } from "../task/types";
|
|
|
14
14
|
|
|
15
15
|
export const MAIN_AGENT_ID = "Main";
|
|
16
16
|
|
|
17
|
+
/** Sidecar marker retained beside a child transcript after an explicit kill. */
|
|
18
|
+
export const AGENT_TOMBSTONE_SUFFIX = ".tombstone";
|
|
19
|
+
|
|
20
|
+
export function getAgentTombstonePath(sessionFile: string): string {
|
|
21
|
+
return `${sessionFile}${AGENT_TOMBSTONE_SUFFIX}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
17
24
|
/**
|
|
18
25
|
* - `running`: a turn is in flight.
|
|
19
26
|
* - `idle`: live AgentSession in memory, awaiting work. Finished agents are
|
|
@@ -22,6 +29,8 @@ export const MAIN_AGENT_ID = "Main";
|
|
|
22
29
|
* - `aborted`: hard-killed, terminal.
|
|
23
30
|
*/
|
|
24
31
|
export type AgentStatus = "running" | "idle" | "parked" | "aborted";
|
|
32
|
+
/** Provenance of a displayed duration: active runtime, transcript span, or unavailable. */
|
|
33
|
+
export type AgentDurationKind = "active" | "span" | "unknown";
|
|
25
34
|
/**
|
|
26
35
|
* - `main`/`sub`: the user-facing agent tree (driving agent + task subagents).
|
|
27
36
|
* - `advisor`: a passive review transcript persisted like a subagent for usage
|
|
@@ -30,6 +39,35 @@ export type AgentStatus = "running" | "idle" | "parked" | "aborted";
|
|
|
30
39
|
*/
|
|
31
40
|
export type AgentKind = "main" | "sub" | "advisor";
|
|
32
41
|
|
|
42
|
+
/** Persisted per-agent totals reconstructed from the child session transcript. */
|
|
43
|
+
export interface AgentMetricsSummary {
|
|
44
|
+
tokens: number;
|
|
45
|
+
requests: number;
|
|
46
|
+
tools: number;
|
|
47
|
+
cost: number;
|
|
48
|
+
durationMs: number;
|
|
49
|
+
durationKind?: AgentDurationKind;
|
|
50
|
+
contextTokens?: number;
|
|
51
|
+
contextWindow?: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Historical identity and telemetry that remain available after the live session is disposed. */
|
|
55
|
+
export interface AgentHistorySummary {
|
|
56
|
+
agent?: string;
|
|
57
|
+
modelRole?: string;
|
|
58
|
+
resolvedModel?: string;
|
|
59
|
+
/** Whether the last resolved model was selected by retry fallback routing. */
|
|
60
|
+
resolvedModelIsFallback?: boolean;
|
|
61
|
+
metrics?: AgentMetricsSummary;
|
|
62
|
+
readOnly?: boolean;
|
|
63
|
+
/** Durable task output artifact, when the executor wrote one. */
|
|
64
|
+
outputPath?: string;
|
|
65
|
+
/** Captured isolated-worktree patch, when patch capture succeeded. */
|
|
66
|
+
patchPath?: string;
|
|
67
|
+
/** Isolated branch identity, when branch-mode capture succeeded. */
|
|
68
|
+
branchName?: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
33
71
|
export interface AgentRef {
|
|
34
72
|
id: string;
|
|
35
73
|
displayName: string;
|
|
@@ -43,6 +81,8 @@ export interface AgentRef {
|
|
|
43
81
|
lastActivity: number;
|
|
44
82
|
/** Short gist of what the agent is currently doing (latest intent or tool), for the work-aware roster. Display-only. */
|
|
45
83
|
activity?: string;
|
|
84
|
+
/** Persisted identity and telemetry restored after the live observer is gone. */
|
|
85
|
+
history?: AgentHistorySummary;
|
|
46
86
|
}
|
|
47
87
|
|
|
48
88
|
export type AgentRefExpectation = AgentRef | AgentSession;
|
|
@@ -50,6 +90,7 @@ export type AgentRefExpectation = AgentRef | AgentSession;
|
|
|
50
90
|
export type RegistryEvent =
|
|
51
91
|
| { type: "registered"; ref: AgentRef }
|
|
52
92
|
| { type: "status_changed"; ref: AgentRef }
|
|
93
|
+
| { type: "metadata_changed"; ref: AgentRef }
|
|
53
94
|
| { type: "removed"; ref: AgentRef };
|
|
54
95
|
|
|
55
96
|
type RegistryListener = (event: RegistryEvent) => void;
|
|
@@ -62,6 +103,14 @@ export interface RegisterInput {
|
|
|
62
103
|
session: AgentSession | null;
|
|
63
104
|
sessionFile?: string | null;
|
|
64
105
|
status?: AgentStatus;
|
|
106
|
+
/** Last persisted task summary, when restoring a historical agent. */
|
|
107
|
+
activity?: string;
|
|
108
|
+
/** Original registration timestamp, when known from persisted history. */
|
|
109
|
+
createdAt?: number;
|
|
110
|
+
/** Last transcript activity timestamp, when known from persisted history. */
|
|
111
|
+
lastActivity?: number;
|
|
112
|
+
/** Persisted identity and telemetry restored after the live observer is gone. */
|
|
113
|
+
history?: AgentHistorySummary;
|
|
65
114
|
}
|
|
66
115
|
|
|
67
116
|
export class AgentRegistry {
|
|
@@ -96,8 +145,10 @@ export class AgentRegistry {
|
|
|
96
145
|
status: input.status ?? "running",
|
|
97
146
|
session: input.session,
|
|
98
147
|
sessionFile: input.sessionFile ?? null,
|
|
99
|
-
createdAt: now,
|
|
100
|
-
lastActivity: now,
|
|
148
|
+
createdAt: input.createdAt ?? now,
|
|
149
|
+
lastActivity: input.lastActivity ?? now,
|
|
150
|
+
activity: input.activity,
|
|
151
|
+
history: input.history,
|
|
101
152
|
};
|
|
102
153
|
this.#refs.set(ref.id, ref);
|
|
103
154
|
this.#emit({ type: "registered", ref });
|
|
@@ -116,6 +167,18 @@ export class AgentRegistry {
|
|
|
116
167
|
return current === expected && current.status === "parked" && !current.session ? current : undefined;
|
|
117
168
|
}
|
|
118
169
|
|
|
170
|
+
/** Attach transcript-derived identity and telemetry without changing lifecycle state. */
|
|
171
|
+
setHistory(id: string, history: AgentHistorySummary, expectedSessionFile?: string): boolean {
|
|
172
|
+
const ref = this.#refs.get(id);
|
|
173
|
+
if (!ref || (expectedSessionFile !== undefined && ref.sessionFile !== expectedSessionFile)) return false;
|
|
174
|
+
const definedHistory = Object.fromEntries(
|
|
175
|
+
Object.entries(history).filter(([, value]) => value !== undefined),
|
|
176
|
+
) as AgentHistorySummary;
|
|
177
|
+
ref.history = { ...ref.history, ...definedHistory };
|
|
178
|
+
this.#emit({ type: "metadata_changed", ref });
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
|
|
119
182
|
setStatus(id: string, status: AgentStatus, expected?: AgentRefExpectation): boolean {
|
|
120
183
|
const ref = this.#refs.get(id);
|
|
121
184
|
if (!ref || !this.#matchesExpected(ref, expected)) return false;
|