@gajae-code/coding-agent 0.3.0 → 0.3.1
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 +18 -0
- package/dist/types/async/job-manager.d.ts +7 -0
- package/dist/types/cli/args.d.ts +1 -1
- package/dist/types/commands/deep-interview.d.ts +3 -0
- package/dist/types/config/keybindings.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +4 -4
- package/dist/types/debug/crash-diagnostics.d.ts +45 -0
- package/dist/types/debug/runtime-gauges.d.ts +6 -0
- package/dist/types/deep-interview/render-middleware.d.ts +1 -0
- package/dist/types/eval/py/executor.d.ts +2 -0
- package/dist/types/eval/py/kernel.d.ts +2 -0
- package/dist/types/exec/bash-executor.d.ts +10 -0
- package/dist/types/gjc-runtime/cli-write-receipt.d.ts +24 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +1 -0
- package/dist/types/gjc-runtime/state-migrations.d.ts +9 -0
- package/dist/types/gjc-runtime/state-schema.d.ts +317 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +10 -0
- package/dist/types/gjc-runtime/workflow-command-ref.d.ts +43 -0
- package/dist/types/harness-control-plane/control-endpoint.d.ts +3 -2
- package/dist/types/hooks/skill-state.d.ts +21 -0
- package/dist/types/internal-urls/agent-protocol.d.ts +2 -2
- package/dist/types/internal-urls/artifact-protocol.d.ts +2 -2
- package/dist/types/internal-urls/registry-helpers.d.ts +8 -7
- package/dist/types/internal-urls/types.d.ts +4 -0
- package/dist/types/lsp/index.d.ts +10 -10
- package/dist/types/modes/bridge/auth.d.ts +12 -0
- package/dist/types/modes/bridge/bridge-client-bridge.d.ts +9 -0
- package/dist/types/modes/bridge/bridge-mode.d.ts +44 -0
- package/dist/types/modes/bridge/bridge-ui-context.d.ts +88 -0
- package/dist/types/modes/bridge/event-stream.d.ts +8 -0
- package/dist/types/modes/components/custom-editor.d.ts +6 -0
- package/dist/types/modes/components/jobs-overlay-model.d.ts +31 -0
- package/dist/types/modes/components/jobs-overlay.d.ts +30 -0
- package/dist/types/modes/components/status-line/types.d.ts +2 -0
- package/dist/types/modes/components/status-line.d.ts +2 -0
- package/dist/types/modes/controllers/input-controller.d.ts +1 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +8 -0
- package/dist/types/modes/index.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/jobs-observer.d.ts +57 -0
- package/dist/types/modes/rpc/host-tools.d.ts +1 -16
- package/dist/types/modes/rpc/host-uris.d.ts +1 -38
- package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +20 -0
- package/dist/types/modes/shared/agent-wire/command-validation.d.ts +2 -0
- package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +24 -0
- package/dist/types/modes/shared/agent-wire/handshake.d.ts +46 -0
- package/dist/types/modes/shared/agent-wire/host-tool-bridge.d.ts +16 -0
- package/dist/types/modes/shared/agent-wire/host-uri-bridge.d.ts +17 -0
- package/dist/types/modes/shared/agent-wire/protocol.d.ts +44 -0
- package/dist/types/modes/shared/agent-wire/responses.d.ts +4 -0
- package/dist/types/modes/shared/agent-wire/scopes.d.ts +18 -0
- package/dist/types/modes/shared/agent-wire/ui-request-broker.d.ts +42 -0
- package/dist/types/modes/shared/agent-wire/ui-result.d.ts +27 -0
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +11 -1
- package/dist/types/skill-state/workflow-state-contract.d.ts +1 -2
- package/dist/types/skill-state/workflow-state-version.d.ts +3 -0
- package/dist/types/task/id.d.ts +7 -0
- package/dist/types/task/index.d.ts +5 -0
- package/dist/types/task/receipt.d.ts +85 -0
- package/dist/types/task/spawn-gate.d.ts +38 -0
- package/dist/types/task/types.d.ts +143 -11
- package/dist/types/tools/cron.d.ts +6 -0
- package/dist/types/tools/index.d.ts +2 -0
- package/dist/types/tools/path-utils.d.ts +1 -0
- package/dist/types/tools/subagent.d.ts +15 -0
- package/package.json +7 -7
- package/scripts/build-binary.ts +7 -0
- package/src/async/job-manager.ts +36 -0
- package/src/cli/args.ts +9 -2
- package/src/commands/deep-interview.ts +1 -0
- package/src/commands/harness.ts +289 -19
- package/src/commands/launch.ts +2 -2
- package/src/commands/state.ts +2 -1
- package/src/commands/team.ts +22 -4
- package/src/config/keybindings.ts +6 -0
- package/src/config/settings-schema.ts +6 -3
- package/src/dap/client.ts +17 -3
- package/src/debug/crash-diagnostics.ts +223 -0
- package/src/debug/runtime-gauges.ts +20 -0
- package/src/deep-interview/render-middleware.ts +6 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
- package/src/defaults/gjc/skills/ralplan/SKILL.md +31 -2
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +28 -2
- package/src/eval/py/executor.ts +21 -1
- package/src/eval/py/kernel.ts +15 -0
- package/src/exec/bash-executor.ts +41 -0
- package/src/gjc-runtime/cli-write-receipt.ts +31 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +69 -32
- package/src/gjc-runtime/ralplan-runtime.ts +213 -36
- package/src/gjc-runtime/state-migrations.ts +54 -7
- package/src/gjc-runtime/state-runtime.ts +461 -64
- package/src/gjc-runtime/state-schema.ts +192 -0
- package/src/gjc-runtime/state-writer.ts +32 -1
- package/src/gjc-runtime/team-runtime.ts +177 -105
- package/src/gjc-runtime/ultragoal-runtime.ts +114 -26
- package/src/gjc-runtime/workflow-command-ref.ts +239 -0
- package/src/gjc-runtime/workflow-manifest.generated.json +108 -4
- package/src/gjc-runtime/workflow-manifest.ts +3 -1
- package/src/harness-control-plane/control-endpoint.ts +19 -8
- package/src/harness-control-plane/owner.ts +57 -10
- package/src/harness-control-plane/state-machine.ts +2 -1
- package/src/hooks/skill-state.ts +176 -26
- package/src/internal-urls/agent-protocol.ts +68 -21
- package/src/internal-urls/artifact-protocol.ts +12 -17
- package/src/internal-urls/docs-index.generated.ts +3 -2
- package/src/internal-urls/registry-helpers.ts +19 -16
- package/src/internal-urls/types.ts +4 -0
- package/src/lsp/client.ts +18 -2
- package/src/main.ts +21 -5
- package/src/modes/bridge/auth.ts +41 -0
- package/src/modes/bridge/bridge-client-bridge.ts +47 -0
- package/src/modes/bridge/bridge-mode.ts +520 -0
- package/src/modes/bridge/bridge-ui-context.ts +200 -0
- package/src/modes/bridge/event-stream.ts +70 -0
- package/src/modes/components/custom-editor.ts +101 -0
- package/src/modes/components/hook-selector.ts +61 -18
- package/src/modes/components/jobs-overlay-model.ts +109 -0
- package/src/modes/components/jobs-overlay.ts +172 -0
- package/src/modes/components/status-line/presets.ts +7 -5
- package/src/modes/components/status-line/segments.ts +25 -0
- package/src/modes/components/status-line/types.ts +2 -0
- package/src/modes/components/status-line.ts +9 -1
- package/src/modes/controllers/extension-ui-controller.ts +39 -3
- package/src/modes/controllers/input-controller.ts +97 -9
- package/src/modes/controllers/selector-controller.ts +29 -0
- package/src/modes/index.ts +1 -0
- package/src/modes/interactive-mode.ts +27 -0
- package/src/modes/jobs-observer.ts +204 -0
- package/src/modes/rpc/host-tools.ts +1 -186
- package/src/modes/rpc/host-uris.ts +1 -235
- package/src/modes/rpc/rpc-client.ts +25 -10
- package/src/modes/rpc/rpc-mode.ts +12 -381
- package/src/modes/shared/agent-wire/command-dispatch.ts +341 -0
- package/src/modes/shared/agent-wire/command-validation.ts +131 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +108 -0
- package/src/modes/shared/agent-wire/handshake.ts +117 -0
- package/src/modes/shared/agent-wire/host-tool-bridge.ts +194 -0
- package/src/modes/shared/agent-wire/host-uri-bridge.ts +236 -0
- package/src/modes/shared/agent-wire/protocol.ts +96 -0
- package/src/modes/shared/agent-wire/responses.ts +17 -0
- package/src/modes/shared/agent-wire/scopes.ts +89 -0
- package/src/modes/shared/agent-wire/ui-request-broker.ts +150 -0
- package/src/modes/shared/agent-wire/ui-result.ts +48 -0
- package/src/modes/types.ts +1 -0
- package/src/prompts/tools/subagent.md +12 -7
- package/src/prompts/tools/task-summary.md +3 -9
- package/src/prompts/tools/task.md +5 -1
- package/src/sdk.ts +4 -0
- package/src/session/agent-session.ts +214 -38
- package/src/skill-state/deep-interview-mutation-guard.ts +23 -4
- package/src/skill-state/workflow-state-contract.ts +7 -4
- package/src/skill-state/workflow-state-version.ts +3 -0
- package/src/slash-commands/builtin-registry.ts +8 -0
- package/src/task/executor.ts +29 -5
- package/src/task/id.ts +33 -0
- package/src/task/index.ts +257 -67
- package/src/task/output-manager.ts +5 -4
- package/src/task/receipt.ts +297 -0
- package/src/task/render.ts +48 -131
- package/src/task/spawn-gate.ts +132 -0
- package/src/task/types.ts +48 -7
- package/src/tools/ask.ts +73 -33
- package/src/tools/ast-edit.ts +1 -0
- package/src/tools/ast-grep.ts +1 -0
- package/src/tools/bash.ts +1 -1
- package/src/tools/cron.ts +48 -0
- package/src/tools/find.ts +4 -1
- package/src/tools/index.ts +2 -0
- package/src/tools/path-utils.ts +3 -2
- package/src/tools/read.ts +1 -0
- package/src/tools/search.ts +1 -0
- package/src/tools/skill.ts +6 -1
- package/src/tools/subagent.ts +237 -84
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
} from "@gajae-code/tui";
|
|
29
29
|
import { APP_NAME, adjustHsv, getProjectDir, hsvToRgb, isEnoent, logger, postmortem, prompt } from "@gajae-code/utils";
|
|
30
30
|
import chalk from "chalk";
|
|
31
|
+
import { AsyncJobManager } from "../async";
|
|
31
32
|
import { KeybindingsManager } from "../config/keybindings";
|
|
32
33
|
import { isSettingsInitialized, type Settings, settings } from "../config/settings";
|
|
33
34
|
import { DEFAULT_GJC_DEFINITION_NAMES } from "../defaults/gjc-defaults";
|
|
@@ -88,6 +89,7 @@ import { InputController } from "./controllers/input-controller";
|
|
|
88
89
|
import { SelectorController } from "./controllers/selector-controller";
|
|
89
90
|
import { SSHCommandController } from "./controllers/ssh-command-controller";
|
|
90
91
|
import { TodoCommandController } from "./controllers/todo-command-controller";
|
|
92
|
+
import { JobsObserver } from "./jobs-observer";
|
|
91
93
|
import { OAuthManualInputManager } from "./oauth-manual-input";
|
|
92
94
|
import { SessionObserverRegistry } from "./session-observer-registry";
|
|
93
95
|
import { interruptHint } from "./shared";
|
|
@@ -330,6 +332,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
330
332
|
#voicePreviousUseTerminalCursor: boolean | null = null;
|
|
331
333
|
#resizeHandler?: () => void;
|
|
332
334
|
#observerRegistry: SessionObserverRegistry;
|
|
335
|
+
#jobsObserver?: JobsObserver;
|
|
333
336
|
#eventBus?: EventBus;
|
|
334
337
|
#eventBusUnsubscribers: Array<() => void> = [];
|
|
335
338
|
#welcomeComponent?: WelcomeComponent;
|
|
@@ -525,6 +528,19 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
525
528
|
this.ui.requestRender();
|
|
526
529
|
});
|
|
527
530
|
|
|
531
|
+
// Event-driven monitor/cron jobs widget. Scoped to this session's owner so
|
|
532
|
+
// overlay actions cannot mutate another agent's background work.
|
|
533
|
+
const jobManager = AsyncJobManager.instance();
|
|
534
|
+
if (jobManager) {
|
|
535
|
+
const jobsObserver = new JobsObserver(jobManager, this.session.getAgentId());
|
|
536
|
+
this.#jobsObserver = jobsObserver;
|
|
537
|
+
this.statusLine.setJobs(jobsObserver.getSnapshot());
|
|
538
|
+
jobsObserver.onChange(() => {
|
|
539
|
+
this.statusLine.setJobs(jobsObserver.getSnapshot());
|
|
540
|
+
this.ui.requestRender();
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
|
|
528
544
|
// Load initial todos
|
|
529
545
|
await this.#loadTodoList();
|
|
530
546
|
|
|
@@ -1843,6 +1859,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1843
1859
|
this.#observerRegistry.dispose();
|
|
1844
1860
|
this.#eventController.dispose();
|
|
1845
1861
|
this.statusLine.dispose();
|
|
1862
|
+
this.#jobsObserver?.dispose();
|
|
1863
|
+
this.editor.dispose();
|
|
1846
1864
|
if (this.#resizeHandler) {
|
|
1847
1865
|
process.stdout.removeListener("resize", this.#resizeHandler);
|
|
1848
1866
|
this.#resizeHandler = undefined;
|
|
@@ -1944,6 +1962,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1944
1962
|
nextEditor.setHistoryStorage(this.historyStorage);
|
|
1945
1963
|
}
|
|
1946
1964
|
nextEditor.setText(previousText);
|
|
1965
|
+
previousEditor.dispose();
|
|
1947
1966
|
|
|
1948
1967
|
this.editorContainer.clear();
|
|
1949
1968
|
this.editor = nextEditor;
|
|
@@ -2317,6 +2336,14 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2317
2336
|
this.#selectorController.showSessionObserver(this.#observerRegistry);
|
|
2318
2337
|
}
|
|
2319
2338
|
|
|
2339
|
+
showJobsOverlay(): void {
|
|
2340
|
+
if (!this.#jobsObserver) {
|
|
2341
|
+
this.showStatus("Background jobs are unavailable in this session");
|
|
2342
|
+
return;
|
|
2343
|
+
}
|
|
2344
|
+
this.#selectorController.showJobsOverlay(this.#jobsObserver);
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2320
2347
|
resetObserverRegistry(): void {
|
|
2321
2348
|
this.#observerRegistry.resetSessions();
|
|
2322
2349
|
this.#observerRegistry.setMainSession(this.sessionManager.getSessionFile() ?? undefined);
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JobsObserver
|
|
3
|
+
*
|
|
4
|
+
* Single, event-driven aggregator over the two background-work sources surfaced
|
|
5
|
+
* by the status-line jobs widget and the jobs overlay:
|
|
6
|
+
* - monitor jobs (bash jobs started by the `monitor` tool, tracked in `AsyncJobManager`)
|
|
7
|
+
* - cron jobs (tracked in the cron module's owner-scoped schedule store)
|
|
8
|
+
*
|
|
9
|
+
* It subscribes to change hooks on both sources (no polling), debounces bursts
|
|
10
|
+
* to a microtask, and exposes a precomputed snapshot so the status-line render
|
|
11
|
+
* loop never scans the underlying stores. A failure latch keeps the widget red
|
|
12
|
+
* until `acknowledgeFailures()` is called (when the overlay opens), so a failed
|
|
13
|
+
* job that evicts before the user looks is not silently lost.
|
|
14
|
+
*/
|
|
15
|
+
import type { AsyncJob, AsyncJobManager } from "../async";
|
|
16
|
+
import { deleteCronJobById, listCronSnapshots, onCronChange } from "../tools/cron";
|
|
17
|
+
|
|
18
|
+
export type JobsWorstState = "none" | "running" | "failed";
|
|
19
|
+
|
|
20
|
+
export interface MonitorJobView {
|
|
21
|
+
id: string;
|
|
22
|
+
label: string;
|
|
23
|
+
status: AsyncJob["status"];
|
|
24
|
+
startTime: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface CronJobView {
|
|
28
|
+
id: string;
|
|
29
|
+
humanSchedule: string;
|
|
30
|
+
cronExpression: string;
|
|
31
|
+
prompt: string;
|
|
32
|
+
recurring: boolean;
|
|
33
|
+
nextFireAt?: number;
|
|
34
|
+
createdAt: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface JobsSnapshot {
|
|
38
|
+
monitors: MonitorJobView[];
|
|
39
|
+
crons: CronJobView[];
|
|
40
|
+
activeMonitorCount: number;
|
|
41
|
+
activeCronCount: number;
|
|
42
|
+
worstState: JobsWorstState;
|
|
43
|
+
failedUnacknowledged: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const EMPTY_JOBS_SNAPSHOT: JobsSnapshot = {
|
|
47
|
+
monitors: [],
|
|
48
|
+
crons: [],
|
|
49
|
+
activeMonitorCount: 0,
|
|
50
|
+
activeCronCount: 0,
|
|
51
|
+
worstState: "none",
|
|
52
|
+
failedUnacknowledged: false,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export class JobsObserver {
|
|
56
|
+
readonly #manager: AsyncJobManager;
|
|
57
|
+
readonly #ownerId: string | undefined;
|
|
58
|
+
readonly #unsubscribers: Array<() => void> = [];
|
|
59
|
+
readonly #listeners = new Set<() => void>();
|
|
60
|
+
#failedUnacknowledged = false;
|
|
61
|
+
#notifyScheduled = false;
|
|
62
|
+
#disposed = false;
|
|
63
|
+
#snapshot: JobsSnapshot = EMPTY_JOBS_SNAPSHOT;
|
|
64
|
+
readonly #acknowledgedFailedIds = new Set<string>();
|
|
65
|
+
|
|
66
|
+
constructor(manager: AsyncJobManager, ownerId: string | undefined) {
|
|
67
|
+
this.#manager = manager;
|
|
68
|
+
this.#ownerId = ownerId;
|
|
69
|
+
this.#unsubscribers.push(manager.onChange(() => this.#onUpstreamChange()));
|
|
70
|
+
this.#unsubscribers.push(onCronChange(() => this.#onUpstreamChange()));
|
|
71
|
+
this.#recompute();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Subscribe to debounced change events. Returns an unsubscribe function. */
|
|
75
|
+
onChange(cb: () => void): () => void {
|
|
76
|
+
this.#listeners.add(cb);
|
|
77
|
+
return () => {
|
|
78
|
+
this.#listeners.delete(cb);
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
#onUpstreamChange(): void {
|
|
83
|
+
if (this.#disposed) return;
|
|
84
|
+
this.#recompute();
|
|
85
|
+
if (this.#notifyScheduled) return;
|
|
86
|
+
this.#notifyScheduled = true;
|
|
87
|
+
queueMicrotask(() => {
|
|
88
|
+
this.#notifyScheduled = false;
|
|
89
|
+
if (this.#disposed) return;
|
|
90
|
+
this.#emit();
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
#emit(): void {
|
|
95
|
+
for (const cb of this.#listeners) {
|
|
96
|
+
try {
|
|
97
|
+
cb();
|
|
98
|
+
} catch {
|
|
99
|
+
// Listener errors are isolated; a bad subscriber must not break others.
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
#listMonitorJobs(): AsyncJob[] {
|
|
105
|
+
const filter = this.#ownerId ? { ownerId: this.#ownerId } : undefined;
|
|
106
|
+
return this.#manager.getAllJobs(filter).filter(job => job.type === "bash" && job.metadata?.monitor === true);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Recompute and store the snapshot. Called on construction and on every
|
|
111
|
+
* upstream change; the status-line render path only reads the stored
|
|
112
|
+
* snapshot (never scans the manager/cron stores).
|
|
113
|
+
*/
|
|
114
|
+
#recompute(): void {
|
|
115
|
+
const monitorJobs = this.#listMonitorJobs();
|
|
116
|
+
const presentIds = new Set(monitorJobs.map(job => job.id));
|
|
117
|
+
// Prune acknowledged ids whose jobs have been evicted.
|
|
118
|
+
for (const id of this.#acknowledgedFailedIds) {
|
|
119
|
+
if (!presentIds.has(id)) this.#acknowledgedFailedIds.delete(id);
|
|
120
|
+
}
|
|
121
|
+
// Sticky failure latch: set when an unacknowledged failed monitor is seen
|
|
122
|
+
// (including at construction); stays set even after the failed job evicts,
|
|
123
|
+
// until acknowledgeFailures() clears it.
|
|
124
|
+
const hasUnacknowledgedFailure = monitorJobs.some(
|
|
125
|
+
job => job.status === "failed" && !this.#acknowledgedFailedIds.has(job.id),
|
|
126
|
+
);
|
|
127
|
+
if (hasUnacknowledgedFailure) this.#failedUnacknowledged = true;
|
|
128
|
+
|
|
129
|
+
const activeMonitors = monitorJobs.filter(job => job.status === "running");
|
|
130
|
+
const cronSnapshots = listCronSnapshots(this.#ownerId);
|
|
131
|
+
const monitors: MonitorJobView[] = monitorJobs
|
|
132
|
+
.map(job => ({ id: job.id, label: job.label, status: job.status, startTime: job.startTime }))
|
|
133
|
+
.sort((a, b) => b.startTime - a.startTime);
|
|
134
|
+
const crons: CronJobView[] = cronSnapshots
|
|
135
|
+
.map(snapshot => ({
|
|
136
|
+
id: snapshot.id,
|
|
137
|
+
humanSchedule: snapshot.humanSchedule,
|
|
138
|
+
cronExpression: snapshot.cron_expression,
|
|
139
|
+
prompt: snapshot.prompt,
|
|
140
|
+
recurring: snapshot.recurring,
|
|
141
|
+
nextFireAt: snapshot.nextFireAt,
|
|
142
|
+
createdAt: snapshot.createdAt,
|
|
143
|
+
}))
|
|
144
|
+
.sort((a, b) => b.createdAt - a.createdAt);
|
|
145
|
+
const worstState: JobsWorstState = this.#failedUnacknowledged
|
|
146
|
+
? "failed"
|
|
147
|
+
: activeMonitors.length > 0 || crons.length > 0
|
|
148
|
+
? "running"
|
|
149
|
+
: "none";
|
|
150
|
+
this.#snapshot = {
|
|
151
|
+
monitors,
|
|
152
|
+
crons,
|
|
153
|
+
activeMonitorCount: activeMonitors.length,
|
|
154
|
+
activeCronCount: crons.length,
|
|
155
|
+
worstState,
|
|
156
|
+
failedUnacknowledged: this.#failedUnacknowledged,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Return the precomputed snapshot (recomputed on each upstream change). */
|
|
161
|
+
getSnapshot(): JobsSnapshot {
|
|
162
|
+
return this.#snapshot;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Clear the failure latch (called when the user opens the jobs overlay). */
|
|
166
|
+
acknowledgeFailures(): void {
|
|
167
|
+
for (const job of this.#listMonitorJobs()) {
|
|
168
|
+
if (job.status === "failed") this.#acknowledgedFailedIds.add(job.id);
|
|
169
|
+
}
|
|
170
|
+
if (!this.#failedUnacknowledged) return;
|
|
171
|
+
this.#failedUnacknowledged = false;
|
|
172
|
+
this.#recompute();
|
|
173
|
+
this.#emit();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Cancel a running monitor job. Returns true when the job was cancelled. */
|
|
177
|
+
cancelMonitor(id: string): boolean {
|
|
178
|
+
return this.#manager.cancel(id);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Delete a visible scheduled cron job. Returns true when removed. */
|
|
182
|
+
deleteCron(id: string): boolean {
|
|
183
|
+
return deleteCronJobById(this.#ownerId, id);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Bounded tail of a monitor job's captured output (for the detail view). */
|
|
187
|
+
getMonitorOutput(id: string): string {
|
|
188
|
+
const slice = this.#manager.readOutputSince(id, 0, this.#ownerId ? { ownerId: this.#ownerId } : undefined);
|
|
189
|
+
return slice?.text ?? "";
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
dispose(): void {
|
|
193
|
+
this.#disposed = true;
|
|
194
|
+
for (const unsubscribe of this.#unsubscribers) {
|
|
195
|
+
try {
|
|
196
|
+
unsubscribe();
|
|
197
|
+
} catch {
|
|
198
|
+
// best-effort teardown
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
this.#unsubscribers.length = 0;
|
|
202
|
+
this.#listeners.clear();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
@@ -1,186 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import type { Static, TSchema } from "@gajae-code/ai";
|
|
3
|
-
import { Snowflake } from "@gajae-code/utils";
|
|
4
|
-
import { applyToolProxy } from "../../extensibility/tool-proxy";
|
|
5
|
-
import type { Theme } from "../../modes/theme/theme";
|
|
6
|
-
import type {
|
|
7
|
-
RpcHostToolCallRequest,
|
|
8
|
-
RpcHostToolCancelRequest,
|
|
9
|
-
RpcHostToolDefinition,
|
|
10
|
-
RpcHostToolResult,
|
|
11
|
-
RpcHostToolUpdate,
|
|
12
|
-
} from "./rpc-types";
|
|
13
|
-
|
|
14
|
-
type RpcHostToolOutput = (frame: RpcHostToolCallRequest | RpcHostToolCancelRequest) => void;
|
|
15
|
-
|
|
16
|
-
type PendingHostToolCall = {
|
|
17
|
-
resolve: (result: AgentToolResult<unknown>) => void;
|
|
18
|
-
reject: (error: Error) => void;
|
|
19
|
-
onUpdate?: AgentToolUpdateCallback<unknown>;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
function isAgentToolResult(value: unknown): value is AgentToolResult<unknown> {
|
|
23
|
-
if (!value || typeof value !== "object") return false;
|
|
24
|
-
const content = (value as { content?: unknown }).content;
|
|
25
|
-
return Array.isArray(content);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function isRpcHostToolResult(value: unknown): value is RpcHostToolResult {
|
|
29
|
-
if (!value || typeof value !== "object") return false;
|
|
30
|
-
const frame = value as { type?: unknown; id?: unknown; result?: unknown };
|
|
31
|
-
return frame.type === "host_tool_result" && typeof frame.id === "string" && isAgentToolResult(frame.result);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function isRpcHostToolUpdate(value: unknown): value is RpcHostToolUpdate {
|
|
35
|
-
if (!value || typeof value !== "object") return false;
|
|
36
|
-
const frame = value as { type?: unknown; id?: unknown; partialResult?: unknown };
|
|
37
|
-
return frame.type === "host_tool_update" && typeof frame.id === "string" && isAgentToolResult(frame.partialResult);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
class RpcHostToolAdapter<TParams extends TSchema = TSchema, TTheme extends Theme = Theme>
|
|
41
|
-
implements AgentTool<TParams, unknown, TTheme>
|
|
42
|
-
{
|
|
43
|
-
declare name: string;
|
|
44
|
-
declare label: string;
|
|
45
|
-
declare description: string;
|
|
46
|
-
declare parameters: TParams;
|
|
47
|
-
readonly strict = true;
|
|
48
|
-
concurrency: "shared" | "exclusive" = "shared";
|
|
49
|
-
#bridge: RpcHostToolBridge;
|
|
50
|
-
#definition: RpcHostToolDefinition;
|
|
51
|
-
|
|
52
|
-
constructor(definition: RpcHostToolDefinition, bridge: RpcHostToolBridge) {
|
|
53
|
-
this.#definition = definition;
|
|
54
|
-
this.#bridge = bridge;
|
|
55
|
-
applyToolProxy(definition, this);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
execute(
|
|
59
|
-
toolCallId: string,
|
|
60
|
-
params: Static<TParams>,
|
|
61
|
-
signal?: AbortSignal,
|
|
62
|
-
onUpdate?: AgentToolUpdateCallback<unknown>,
|
|
63
|
-
): Promise<AgentToolResult<unknown>> {
|
|
64
|
-
return this.#bridge.requestExecution(
|
|
65
|
-
this.#definition,
|
|
66
|
-
toolCallId,
|
|
67
|
-
params as Record<string, unknown>,
|
|
68
|
-
signal,
|
|
69
|
-
onUpdate,
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export class RpcHostToolBridge {
|
|
75
|
-
#output: RpcHostToolOutput;
|
|
76
|
-
#definitions = new Map<string, RpcHostToolDefinition>();
|
|
77
|
-
#pendingCalls = new Map<string, PendingHostToolCall>();
|
|
78
|
-
|
|
79
|
-
constructor(output: RpcHostToolOutput) {
|
|
80
|
-
this.#output = output;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
getToolNames(): string[] {
|
|
84
|
-
return Array.from(this.#definitions.keys());
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
setTools(tools: RpcHostToolDefinition[]): AgentTool[] {
|
|
88
|
-
this.#definitions = new Map(tools.map(tool => [tool.name, tool]));
|
|
89
|
-
return tools.map(tool => new RpcHostToolAdapter(tool, this));
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
handleResult(frame: RpcHostToolResult): boolean {
|
|
93
|
-
const pending = this.#pendingCalls.get(frame.id);
|
|
94
|
-
if (!pending) return false;
|
|
95
|
-
this.#pendingCalls.delete(frame.id);
|
|
96
|
-
if (frame.isError) {
|
|
97
|
-
const text = frame.result.content
|
|
98
|
-
.filter(
|
|
99
|
-
(item): item is { type: "text"; text: string } => item.type === "text" && typeof item.text === "string",
|
|
100
|
-
)
|
|
101
|
-
.map(item => item.text)
|
|
102
|
-
.join("\n")
|
|
103
|
-
.trim();
|
|
104
|
-
pending.reject(new Error(text || "Host tool execution failed"));
|
|
105
|
-
return true;
|
|
106
|
-
}
|
|
107
|
-
pending.resolve(frame.result);
|
|
108
|
-
return true;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
handleUpdate(frame: RpcHostToolUpdate): boolean {
|
|
112
|
-
const pending = this.#pendingCalls.get(frame.id);
|
|
113
|
-
if (!pending) return false;
|
|
114
|
-
pending.onUpdate?.(frame.partialResult);
|
|
115
|
-
return true;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
requestExecution(
|
|
119
|
-
definition: RpcHostToolDefinition,
|
|
120
|
-
toolCallId: string,
|
|
121
|
-
args: Record<string, unknown>,
|
|
122
|
-
signal?: AbortSignal,
|
|
123
|
-
onUpdate?: AgentToolUpdateCallback<unknown>,
|
|
124
|
-
): Promise<AgentToolResult<unknown>> {
|
|
125
|
-
if (signal?.aborted) {
|
|
126
|
-
return Promise.reject(new Error(`Host tool "${definition.name}" was aborted`));
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const id = Snowflake.next() as string;
|
|
130
|
-
const { promise, resolve, reject } = Promise.withResolvers<AgentToolResult<unknown>>();
|
|
131
|
-
let settled = false;
|
|
132
|
-
|
|
133
|
-
const cleanup = () => {
|
|
134
|
-
signal?.removeEventListener("abort", onAbort);
|
|
135
|
-
this.#pendingCalls.delete(id);
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
const onAbort = () => {
|
|
139
|
-
if (settled) return;
|
|
140
|
-
settled = true;
|
|
141
|
-
cleanup();
|
|
142
|
-
this.#output({
|
|
143
|
-
type: "host_tool_cancel",
|
|
144
|
-
id: Snowflake.next() as string,
|
|
145
|
-
targetId: id,
|
|
146
|
-
});
|
|
147
|
-
reject(new Error(`Host tool "${definition.name}" was aborted`));
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
151
|
-
this.#pendingCalls.set(id, {
|
|
152
|
-
resolve: result => {
|
|
153
|
-
if (settled) return;
|
|
154
|
-
settled = true;
|
|
155
|
-
cleanup();
|
|
156
|
-
resolve(result);
|
|
157
|
-
},
|
|
158
|
-
reject: error => {
|
|
159
|
-
if (settled) return;
|
|
160
|
-
settled = true;
|
|
161
|
-
cleanup();
|
|
162
|
-
reject(error);
|
|
163
|
-
},
|
|
164
|
-
onUpdate,
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
this.#output({
|
|
168
|
-
type: "host_tool_call",
|
|
169
|
-
id,
|
|
170
|
-
toolCallId,
|
|
171
|
-
toolName: definition.name,
|
|
172
|
-
arguments: args,
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
return promise;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
rejectAllPending(message: string): void {
|
|
179
|
-
const error = new Error(message);
|
|
180
|
-
const pendingCalls = Array.from(this.#pendingCalls.values());
|
|
181
|
-
this.#pendingCalls.clear();
|
|
182
|
-
for (const pending of pendingCalls) {
|
|
183
|
-
pending.reject(error);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
1
|
+
export * from "../shared/agent-wire/host-tool-bridge";
|
|
@@ -1,235 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { InternalUrlRouter } from "../../internal-urls";
|
|
3
|
-
import type {
|
|
4
|
-
InternalResource,
|
|
5
|
-
InternalUrl,
|
|
6
|
-
ProtocolHandler,
|
|
7
|
-
ResolveContext,
|
|
8
|
-
WriteContext,
|
|
9
|
-
} from "../../internal-urls/types";
|
|
10
|
-
import type {
|
|
11
|
-
RpcHostUriCancelRequest,
|
|
12
|
-
RpcHostUriRequest,
|
|
13
|
-
RpcHostUriResult,
|
|
14
|
-
RpcHostUriSchemeDefinition,
|
|
15
|
-
} from "./rpc-types";
|
|
16
|
-
|
|
17
|
-
type RpcHostUriOutput = (frame: RpcHostUriRequest | RpcHostUriCancelRequest) => void;
|
|
18
|
-
|
|
19
|
-
type PendingUriRequest = {
|
|
20
|
-
operation: "read" | "write";
|
|
21
|
-
url: string;
|
|
22
|
-
resolve: (frame: RpcHostUriResult) => void;
|
|
23
|
-
reject: (error: Error) => void;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
/** Type guard for inbound `host_uri_result` frames coming from the host. */
|
|
27
|
-
export function isRpcHostUriResult(value: unknown): value is RpcHostUriResult {
|
|
28
|
-
if (!value || typeof value !== "object") return false;
|
|
29
|
-
const frame = value as { type?: unknown; id?: unknown };
|
|
30
|
-
return frame.type === "host_uri_result" && typeof frame.id === "string";
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* One handler instance per host-registered scheme. Delegates reads and (when
|
|
35
|
-
* the scheme was registered as writable) writes to the bridge, which serializes
|
|
36
|
-
* them over the RPC transport.
|
|
37
|
-
*/
|
|
38
|
-
class RpcHostUriProtocolHandler implements ProtocolHandler {
|
|
39
|
-
readonly scheme: string;
|
|
40
|
-
readonly immutable: boolean;
|
|
41
|
-
readonly write?: (url: InternalUrl, content: string, context?: WriteContext) => Promise<void>;
|
|
42
|
-
readonly #bridge: RpcHostUriBridge;
|
|
43
|
-
|
|
44
|
-
constructor(definition: RpcHostUriSchemeDefinition, bridge: RpcHostUriBridge) {
|
|
45
|
-
this.scheme = definition.scheme;
|
|
46
|
-
this.immutable = definition.immutable === true;
|
|
47
|
-
this.#bridge = bridge;
|
|
48
|
-
if (definition.writable === true) {
|
|
49
|
-
this.write = (url, content, context) => this.#bridge.requestWrite(this.scheme, url, content, context);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
resolve(url: InternalUrl, context?: ResolveContext): Promise<InternalResource> {
|
|
54
|
-
return this.#bridge.requestRead(this.scheme, url, context);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Bidirectional bridge that lets the RPC host own a set of URI schemes.
|
|
60
|
-
*
|
|
61
|
-
* The host registers schemes via `set_host_uri_schemes`; the bridge installs
|
|
62
|
-
* a `RpcHostUriProtocolHandler` per scheme into the process-global
|
|
63
|
-
* {@link InternalUrlRouter}. Reads land on the read tool through the existing
|
|
64
|
-
* router; writes are intercepted by the write tool and dispatched through
|
|
65
|
-
* `requestWrite`.
|
|
66
|
-
*/
|
|
67
|
-
export class RpcHostUriBridge {
|
|
68
|
-
#output: RpcHostUriOutput;
|
|
69
|
-
#router: InternalUrlRouter;
|
|
70
|
-
#definitions = new Map<string, RpcHostUriSchemeDefinition>();
|
|
71
|
-
#pending = new Map<string, PendingUriRequest>();
|
|
72
|
-
|
|
73
|
-
constructor(output: RpcHostUriOutput, router: InternalUrlRouter = InternalUrlRouter.instance()) {
|
|
74
|
-
this.#output = output;
|
|
75
|
-
this.#router = router;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
getSchemes(): string[] {
|
|
79
|
-
return Array.from(this.#definitions.keys());
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Replace the registered set of host URI schemes. Previously registered
|
|
84
|
-
* schemes that no longer appear in the new set are unregistered from the
|
|
85
|
-
* router; surviving and new schemes get fresh handler instances.
|
|
86
|
-
*/
|
|
87
|
-
setSchemes(schemes: RpcHostUriSchemeDefinition[]): string[] {
|
|
88
|
-
const normalized = new Map<string, RpcHostUriSchemeDefinition>();
|
|
89
|
-
for (const raw of schemes) {
|
|
90
|
-
const scheme = typeof raw?.scheme === "string" ? raw.scheme.trim().toLowerCase() : "";
|
|
91
|
-
if (!scheme) {
|
|
92
|
-
throw new Error("Host URI scheme must be a non-empty string");
|
|
93
|
-
}
|
|
94
|
-
if (!/^[a-z][a-z0-9+.-]*$/.test(scheme)) {
|
|
95
|
-
throw new Error(`Host URI scheme contains invalid characters: ${raw.scheme}`);
|
|
96
|
-
}
|
|
97
|
-
normalized.set(scheme, {
|
|
98
|
-
scheme,
|
|
99
|
-
description: typeof raw.description === "string" ? raw.description : undefined,
|
|
100
|
-
writable: raw.writable === true,
|
|
101
|
-
immutable: raw.immutable === true,
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
for (const previous of this.#definitions.keys()) {
|
|
106
|
-
if (!normalized.has(previous)) {
|
|
107
|
-
this.#router.unregister(previous);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
for (const definition of normalized.values()) {
|
|
111
|
-
this.#router.register(new RpcHostUriProtocolHandler(definition, this));
|
|
112
|
-
}
|
|
113
|
-
this.#definitions = normalized;
|
|
114
|
-
return Array.from(normalized.keys());
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Unregister every host scheme from the router and reject any in-flight
|
|
119
|
-
* requests. Called on RPC shutdown to keep the global router clean for
|
|
120
|
-
* subsequent sessions in the same process (used by tests).
|
|
121
|
-
*/
|
|
122
|
-
clear(message: string = "Host URI bridge shut down"): void {
|
|
123
|
-
for (const scheme of this.#definitions.keys()) {
|
|
124
|
-
this.#router.unregister(scheme);
|
|
125
|
-
}
|
|
126
|
-
this.#definitions.clear();
|
|
127
|
-
this.rejectAllPending(message);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/** Resolve a pending request by id; called by `rpc-mode` on inbound results. */
|
|
131
|
-
handleResult(frame: RpcHostUriResult): boolean {
|
|
132
|
-
const pending = this.#pending.get(frame.id);
|
|
133
|
-
if (!pending) return false;
|
|
134
|
-
this.#pending.delete(frame.id);
|
|
135
|
-
pending.resolve(frame);
|
|
136
|
-
return true;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
rejectAllPending(message: string): void {
|
|
140
|
-
const error = new Error(message);
|
|
141
|
-
const pending = Array.from(this.#pending.values());
|
|
142
|
-
this.#pending.clear();
|
|
143
|
-
for (const entry of pending) {
|
|
144
|
-
entry.reject(error);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
async requestRead(scheme: string, url: InternalUrl, context?: ResolveContext): Promise<InternalResource> {
|
|
149
|
-
const result = await this.#dispatch("read", url.href, undefined, context?.signal);
|
|
150
|
-
if (result.isError) {
|
|
151
|
-
throw new Error(result.error || result.content || `Host URI read failed for ${url.href}`);
|
|
152
|
-
}
|
|
153
|
-
const content = result.content ?? "";
|
|
154
|
-
const contentType = result.contentType ?? "text/plain";
|
|
155
|
-
const definition = this.#definitions.get(scheme);
|
|
156
|
-
return {
|
|
157
|
-
url: url.href,
|
|
158
|
-
content,
|
|
159
|
-
contentType,
|
|
160
|
-
size: Buffer.byteLength(content, "utf-8"),
|
|
161
|
-
notes: result.notes && result.notes.length > 0 ? [...result.notes] : undefined,
|
|
162
|
-
immutable: result.immutable ?? definition?.immutable === true,
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
async requestWrite(_scheme: string, url: InternalUrl, content: string, context?: WriteContext): Promise<void> {
|
|
167
|
-
const result = await this.#dispatch("write", url.href, content, context?.signal);
|
|
168
|
-
if (result.isError) {
|
|
169
|
-
throw new Error(result.error || result.content || `Host URI write failed for ${url.href}`);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
#dispatch(
|
|
174
|
-
operation: "read" | "write",
|
|
175
|
-
url: string,
|
|
176
|
-
content: string | undefined,
|
|
177
|
-
signal: AbortSignal | undefined,
|
|
178
|
-
): Promise<RpcHostUriResult> {
|
|
179
|
-
if (signal?.aborted) {
|
|
180
|
-
return Promise.reject(new Error(`Host URI ${operation} for ${url} was aborted`));
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const id = Snowflake.next() as string;
|
|
184
|
-
const { promise, resolve, reject } = Promise.withResolvers<RpcHostUriResult>();
|
|
185
|
-
let settled = false;
|
|
186
|
-
|
|
187
|
-
const cleanup = () => {
|
|
188
|
-
signal?.removeEventListener("abort", onAbort);
|
|
189
|
-
this.#pending.delete(id);
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
const onAbort = () => {
|
|
193
|
-
if (settled) return;
|
|
194
|
-
settled = true;
|
|
195
|
-
cleanup();
|
|
196
|
-
this.#output({
|
|
197
|
-
type: "host_uri_cancel",
|
|
198
|
-
id: Snowflake.next() as string,
|
|
199
|
-
targetId: id,
|
|
200
|
-
});
|
|
201
|
-
reject(new Error(`Host URI ${operation} for ${url} was aborted`));
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
205
|
-
this.#pending.set(id, {
|
|
206
|
-
operation,
|
|
207
|
-
url,
|
|
208
|
-
resolve: frame => {
|
|
209
|
-
if (settled) return;
|
|
210
|
-
settled = true;
|
|
211
|
-
cleanup();
|
|
212
|
-
resolve(frame);
|
|
213
|
-
},
|
|
214
|
-
reject: err => {
|
|
215
|
-
if (settled) return;
|
|
216
|
-
settled = true;
|
|
217
|
-
cleanup();
|
|
218
|
-
reject(err);
|
|
219
|
-
},
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
const frame: RpcHostUriRequest = {
|
|
223
|
-
type: "host_uri_request",
|
|
224
|
-
id,
|
|
225
|
-
operation,
|
|
226
|
-
url,
|
|
227
|
-
};
|
|
228
|
-
if (operation === "write") {
|
|
229
|
-
frame.content = content ?? "";
|
|
230
|
-
}
|
|
231
|
-
this.#output(frame);
|
|
232
|
-
|
|
233
|
-
return promise;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
1
|
+
export * from "../shared/agent-wire/host-uri-bridge";
|