@clinebot/core 0.0.36 → 0.0.37
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/dist/ClineCore.d.ts +312 -3
- package/dist/ClineCore.d.ts.map +1 -1
- package/dist/account/cline-account-service.d.ts.map +1 -1
- package/dist/cron/cron-event-ingress.d.ts +38 -0
- package/dist/cron/cron-event-ingress.d.ts.map +1 -0
- package/dist/cron/cron-materializer.d.ts +36 -0
- package/dist/cron/cron-materializer.d.ts.map +1 -0
- package/dist/cron/cron-reconciler.d.ts +62 -0
- package/dist/cron/cron-reconciler.d.ts.map +1 -0
- package/dist/cron/cron-report-writer.d.ts +41 -0
- package/dist/cron/cron-report-writer.d.ts.map +1 -0
- package/dist/cron/cron-runner.d.ts +43 -0
- package/dist/cron/cron-runner.d.ts.map +1 -0
- package/dist/cron/cron-schema.d.ts +3 -0
- package/dist/cron/cron-schema.d.ts.map +1 -0
- package/dist/cron/cron-service.d.ts +57 -0
- package/dist/cron/cron-service.d.ts.map +1 -0
- package/dist/cron/cron-spec-parser.d.ts +27 -0
- package/dist/cron/cron-spec-parser.d.ts.map +1 -0
- package/dist/cron/cron-watcher.d.ts +23 -0
- package/dist/cron/cron-watcher.d.ts.map +1 -0
- package/dist/cron/scheduler.d.ts +3 -1
- package/dist/cron/scheduler.d.ts.map +1 -1
- package/dist/cron/sqlite-cron-store.d.ts +230 -0
- package/dist/cron/sqlite-cron-store.d.ts.map +1 -0
- package/dist/extensions/plugin/plugin-config-loader.d.ts +7 -1
- package/dist/extensions/plugin/plugin-config-loader.d.ts.map +1 -1
- package/dist/extensions/plugin/plugin-loader.d.ts +10 -6
- package/dist/extensions/plugin/plugin-loader.d.ts.map +1 -1
- package/dist/extensions/plugin/plugin-sandbox.d.ts +7 -1
- package/dist/extensions/plugin/plugin-sandbox.d.ts.map +1 -1
- package/dist/extensions/plugin-sandbox-bootstrap.js +236 -275
- package/dist/extensions/tools/constants.d.ts +1 -0
- package/dist/extensions/tools/constants.d.ts.map +1 -1
- package/dist/extensions/tools/definitions.d.ts +2 -3
- package/dist/extensions/tools/definitions.d.ts.map +1 -1
- package/dist/extensions/tools/executors/editor.d.ts.map +1 -1
- package/dist/extensions/tools/helpers.d.ts +1 -0
- package/dist/extensions/tools/helpers.d.ts.map +1 -1
- package/dist/extensions/tools/index.d.ts +1 -2
- package/dist/extensions/tools/index.d.ts.map +1 -1
- package/dist/extensions/tools/presets.d.ts +1 -1
- package/dist/extensions/tools/schemas.d.ts +25 -3
- package/dist/extensions/tools/schemas.d.ts.map +1 -1
- package/dist/extensions/tools/team/delegated-agent.d.ts +2 -2
- package/dist/extensions/tools/team/delegated-agent.d.ts.map +1 -1
- package/dist/extensions/tools/team/multi-agent.d.ts +7 -3
- package/dist/extensions/tools/team/multi-agent.d.ts.map +1 -1
- package/dist/extensions/tools/team/team-tools.d.ts.map +1 -1
- package/dist/extensions/tools/types.d.ts +0 -5
- package/dist/extensions/tools/types.d.ts.map +1 -1
- package/dist/hooks/hook-bridge.d.ts +118 -0
- package/dist/hooks/hook-bridge.d.ts.map +1 -0
- package/dist/hooks/hook-file-hooks.d.ts +2 -1
- package/dist/hooks/hook-file-hooks.d.ts.map +1 -1
- package/dist/hooks/hook-registry.d.ts +16 -0
- package/dist/hooks/hook-registry.d.ts.map +1 -0
- package/dist/hub/browser-websocket.d.ts.map +1 -1
- package/dist/hub/client.d.ts +7 -1
- package/dist/hub/client.d.ts.map +1 -1
- package/dist/hub/daemon-entry.js +721 -461
- package/dist/hub/daemon.d.ts.map +1 -1
- package/dist/hub/defaults.d.ts +8 -4
- package/dist/hub/defaults.d.ts.map +1 -1
- package/dist/hub/index.js +665 -415
- package/dist/hub/runtime-handlers.d.ts.map +1 -1
- package/dist/hub/server.d.ts +18 -0
- package/dist/hub/server.d.ts.map +1 -1
- package/dist/hub/session-client.d.ts +3 -0
- package/dist/hub/session-client.d.ts.map +1 -1
- package/dist/hub/start-shared-server.d.ts.map +1 -1
- package/dist/hub/ui-client.d.ts +1 -0
- package/dist/hub/ui-client.d.ts.map +1 -1
- package/dist/index.d.ts +9 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +756 -467
- package/dist/llms/cline-recommended-models.d.ts +20 -0
- package/dist/llms/cline-recommended-models.d.ts.map +1 -0
- package/dist/llms/handler-factory.d.ts +16 -0
- package/dist/llms/handler-factory.d.ts.map +1 -0
- package/dist/llms/provider-defaults.d.ts.map +1 -1
- package/dist/llms/provider-settings.d.ts +45 -2
- package/dist/llms/provider-settings.d.ts.map +1 -1
- package/dist/llms/runtime-registry.d.ts.map +1 -1
- package/dist/runtime/agent-config-adapter.d.ts +148 -0
- package/dist/runtime/agent-config-adapter.d.ts.map +1 -0
- package/dist/runtime/agent-runtime-config-builder.d.ts +96 -0
- package/dist/runtime/agent-runtime-config-builder.d.ts.map +1 -0
- package/dist/runtime/history.d.ts +6 -0
- package/dist/runtime/history.d.ts.map +1 -1
- package/dist/runtime/host.d.ts.map +1 -1
- package/dist/runtime/loop-detection.d.ts +59 -0
- package/dist/runtime/loop-detection.d.ts.map +1 -0
- package/dist/runtime/mistake-tracker.d.ts +69 -0
- package/dist/runtime/mistake-tracker.d.ts.map +1 -0
- package/dist/runtime/runtime-builder.d.ts.map +1 -1
- package/dist/runtime/runtime-event-adapter.d.ts +102 -0
- package/dist/runtime/runtime-event-adapter.d.ts.map +1 -0
- package/dist/runtime/runtime-host.d.ts +28 -3
- package/dist/runtime/runtime-host.d.ts.map +1 -1
- package/dist/runtime/session-runtime-orchestrator.d.ts +261 -0
- package/dist/runtime/session-runtime-orchestrator.d.ts.map +1 -0
- package/dist/runtime/session-runtime.d.ts +16 -3
- package/dist/runtime/session-runtime.d.ts.map +1 -1
- package/dist/runtime/user-input-builder.d.ts +24 -0
- package/dist/runtime/user-input-builder.d.ts.map +1 -0
- package/dist/services/index.js +28 -0
- package/dist/services/local-runtime-bootstrap.d.ts.map +1 -1
- package/dist/services/plugin-tools.d.ts.map +1 -1
- package/dist/services/providers/local-provider-registry.d.ts +197 -21
- package/dist/services/providers/local-provider-registry.d.ts.map +1 -1
- package/dist/services/providers/local-provider-service.d.ts +3 -1
- package/dist/services/providers/local-provider-service.d.ts.map +1 -1
- package/dist/services/session-data.d.ts.map +1 -1
- package/dist/services/session-telemetry.d.ts +7 -2
- package/dist/services/session-telemetry.d.ts.map +1 -1
- package/dist/services/storage/file-team-store.d.ts.map +1 -1
- package/dist/services/storage/provider-settings-legacy-migration.d.ts.map +1 -1
- package/dist/services/storage/provider-settings-manager.d.ts +1 -0
- package/dist/services/storage/provider-settings-manager.d.ts.map +1 -1
- package/dist/services/storage/sqlite-team-store.d.ts.map +1 -1
- package/dist/session/conversation-store.d.ts +30 -0
- package/dist/session/conversation-store.d.ts.map +1 -0
- package/dist/session/message-builder.d.ts +65 -0
- package/dist/session/message-builder.d.ts.map +1 -0
- package/dist/session/session-manifest.d.ts +1 -1
- package/dist/transports/hub.d.ts +14 -3
- package/dist/transports/hub.d.ts.map +1 -1
- package/dist/transports/local.d.ts +14 -4
- package/dist/transports/local.d.ts.map +1 -1
- package/dist/transports/remote.d.ts.map +1 -1
- package/dist/types/chat-schema.d.ts +5 -5
- package/dist/types/config.d.ts +9 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/events.d.ts +7 -6
- package/dist/types/events.d.ts.map +1 -1
- package/dist/types/provider-settings.d.ts +2 -2
- package/dist/types/provider-settings.d.ts.map +1 -1
- package/dist/types/session.d.ts +5 -2
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types.d.ts +4 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/ClineCore.ts +691 -6
- package/src/account/cline-account-service.ts +44 -6
- package/src/cron/cron-event-ingress.ts +357 -0
- package/src/cron/cron-materializer.ts +97 -0
- package/src/cron/cron-reconciler.ts +241 -0
- package/src/cron/cron-report-writer.ts +153 -0
- package/src/cron/cron-runner.ts +495 -0
- package/src/cron/cron-schema.ts +127 -0
- package/src/cron/cron-service.ts +163 -0
- package/src/cron/cron-spec-parser.ts +489 -0
- package/src/cron/cron-watcher.ts +102 -0
- package/src/cron/index.ts +10 -0
- package/src/cron/scheduler.ts +141 -6
- package/src/cron/sqlite-cron-store.ts +1286 -0
- package/src/extensions/plugin/plugin-config-loader.ts +21 -1
- package/src/extensions/plugin/plugin-loader.ts +25 -9
- package/src/extensions/plugin/plugin-sandbox-bootstrap.ts +151 -1
- package/src/extensions/plugin/plugin-sandbox.ts +131 -7
- package/src/extensions/tools/constants.ts +2 -0
- package/src/extensions/tools/definitions.ts +31 -22
- package/src/extensions/tools/executors/editor.ts +4 -3
- package/src/extensions/tools/helpers.ts +24 -0
- package/src/extensions/tools/index.ts +1 -2
- package/src/extensions/tools/presets.ts +1 -1
- package/src/extensions/tools/schemas.ts +13 -18
- package/src/extensions/tools/team/delegated-agent.ts +8 -3
- package/src/extensions/tools/team/multi-agent.ts +135 -19
- package/src/extensions/tools/team/team-tools.ts +151 -91
- package/src/extensions/tools/types.ts +0 -6
- package/src/hooks/hook-bridge.ts +489 -0
- package/src/hooks/hook-file-hooks.ts +58 -3
- package/src/hooks/hook-registry.ts +257 -0
- package/src/hub/browser-websocket.ts +26 -4
- package/src/hub/client.ts +72 -13
- package/src/hub/daemon-entry.ts +35 -0
- package/src/hub/daemon.ts +117 -14
- package/src/hub/defaults.ts +39 -12
- package/src/hub/runtime-handlers.ts +4 -3
- package/src/hub/server.ts +506 -77
- package/src/hub/session-client.ts +43 -1
- package/src/hub/start-shared-server.ts +3 -0
- package/src/hub/ui-client.ts +4 -0
- package/src/index.ts +46 -1
- package/src/llms/cline-recommended-models.ts +167 -0
- package/src/llms/handler-factory.ts +56 -0
- package/src/llms/provider-defaults.ts +17 -1
- package/src/llms/provider-settings.ts +48 -1
- package/src/llms/runtime-registry.ts +1 -0
- package/src/runtime/agent-config-adapter.ts +636 -0
- package/src/runtime/agent-runtime-config-builder.ts +205 -0
- package/src/runtime/error-feedback.ts +142 -0
- package/src/runtime/history.ts +137 -0
- package/src/runtime/host.ts +22 -0
- package/src/runtime/loop-detection.ts +162 -0
- package/src/runtime/mistake-tracker.ts +221 -0
- package/src/runtime/runtime-builder.ts +61 -5
- package/src/runtime/runtime-event-adapter.ts +412 -0
- package/src/runtime/runtime-host.ts +45 -1
- package/src/runtime/session-runtime-orchestrator.ts +1253 -0
- package/src/runtime/session-runtime.ts +16 -2
- package/src/runtime/user-input-builder.ts +167 -0
- package/src/services/local-runtime-bootstrap.ts +128 -22
- package/src/services/plugin-tools.ts +1 -0
- package/src/services/providers/local-provider-registry.ts +273 -57
- package/src/services/providers/local-provider-service.ts +67 -7
- package/src/services/session-data.ts +16 -14
- package/src/services/session-telemetry.ts +6 -15
- package/src/services/storage/file-team-store.ts +1 -5
- package/src/services/storage/provider-settings-legacy-migration.ts +8 -47
- package/src/services/storage/provider-settings-manager.ts +16 -1
- package/src/services/storage/sqlite-team-store.ts +1 -5
- package/src/session/conversation-store.ts +77 -0
- package/src/session/message-builder.ts +941 -0
- package/src/transports/hub.ts +458 -33
- package/src/transports/local.ts +296 -65
- package/src/transports/remote.ts +1 -0
- package/src/types/config.ts +9 -0
- package/src/types/events.ts +8 -6
- package/src/types/index.ts +3 -0
- package/src/types/provider-settings.ts +8 -1
- package/src/types/session.ts +5 -2
- package/src/types.ts +15 -1
- package/dist/cron/index.d.ts +0 -6
- package/dist/cron/index.d.ts.map +0 -1
- package/dist/services/telemetry/index.js +0 -28
package/src/hub/server.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
SessionRecord as HubSessionRecord,
|
|
10
10
|
HubToolExecutorName,
|
|
11
11
|
JsonValue,
|
|
12
|
+
RuntimeConfigExtensionKind,
|
|
12
13
|
SessionParticipant,
|
|
13
14
|
TeamProgressProjectionEvent,
|
|
14
15
|
ToolApprovalRequest,
|
|
@@ -16,6 +17,7 @@ import type {
|
|
|
16
17
|
} from "@clinebot/shared";
|
|
17
18
|
import { createSessionId } from "@clinebot/shared";
|
|
18
19
|
import { WebSocketServer } from "ws";
|
|
20
|
+
import { CronService, type CronServiceOptions } from "../cron/cron-service";
|
|
19
21
|
import { HubScheduleCommandService } from "../cron/schedule-command-service";
|
|
20
22
|
import {
|
|
21
23
|
type HubScheduleRuntimeHandlers,
|
|
@@ -32,10 +34,11 @@ import { SqliteSessionStore } from "../services/storage/sqlite-session-store";
|
|
|
32
34
|
import { CoreSessionService } from "../session/session-service";
|
|
33
35
|
import { LocalRuntimeHost } from "../transports/local";
|
|
34
36
|
import { readPersistedMessagesFile } from "../transports/runtime-host-support";
|
|
35
|
-
import type { CoreSessionEvent } from "../types/events";
|
|
37
|
+
import type { CoreSessionEvent, SessionPendingPrompt } from "../types/events";
|
|
36
38
|
import type { SessionRecord as LocalSessionRecord } from "../types/sessions";
|
|
37
39
|
import { BrowserWebSocketHubAdapter } from "./browser-websocket";
|
|
38
40
|
import { verifyHubConnection } from "./client";
|
|
41
|
+
import { resolveDefaultHubPort } from "./defaults";
|
|
39
42
|
import {
|
|
40
43
|
clearHubDiscovery,
|
|
41
44
|
createHubServerUrl,
|
|
@@ -60,8 +63,15 @@ type NodeWebSocketLike = {
|
|
|
60
63
|
once(event: "close", listener: () => void): void;
|
|
61
64
|
};
|
|
62
65
|
|
|
66
|
+
type NodeUpgradeSocketLike = {
|
|
67
|
+
destroy(error?: Error): void;
|
|
68
|
+
write(chunk: string): boolean;
|
|
69
|
+
end(): void;
|
|
70
|
+
};
|
|
71
|
+
|
|
63
72
|
type HubSessionState = {
|
|
64
73
|
createdByClientId: string;
|
|
74
|
+
interactive: boolean;
|
|
65
75
|
participants: Map<string, SessionParticipant>;
|
|
66
76
|
};
|
|
67
77
|
|
|
@@ -104,6 +114,37 @@ function wrapWsSocket(socket: NodeWebSocketLike) {
|
|
|
104
114
|
};
|
|
105
115
|
}
|
|
106
116
|
|
|
117
|
+
const RUNTIME_CONFIG_EXTENSION_KINDS = new Set<RuntimeConfigExtensionKind>([
|
|
118
|
+
"rules",
|
|
119
|
+
"skills",
|
|
120
|
+
"plugins",
|
|
121
|
+
]);
|
|
122
|
+
|
|
123
|
+
function parseRuntimeConfigExtensions(
|
|
124
|
+
value: unknown,
|
|
125
|
+
): RuntimeConfigExtensionKind[] | undefined {
|
|
126
|
+
if (!Array.isArray(value)) {
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
const extensions = value
|
|
130
|
+
.map((item) => (typeof item === "string" ? item.trim() : ""))
|
|
131
|
+
.filter((item): item is RuntimeConfigExtensionKind =>
|
|
132
|
+
RUNTIME_CONFIG_EXTENSION_KINDS.has(item as RuntimeConfigExtensionKind),
|
|
133
|
+
);
|
|
134
|
+
return [...new Set(extensions)];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function rejectUpgradeSocket(socket: NodeUpgradeSocketLike): void {
|
|
138
|
+
try {
|
|
139
|
+
socket.write(
|
|
140
|
+
"HTTP/1.1 400 Bad Request\r\nConnection: close\r\nContent-Length: 0\r\n\r\n",
|
|
141
|
+
);
|
|
142
|
+
socket.end();
|
|
143
|
+
} catch {
|
|
144
|
+
socket.destroy();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
107
148
|
function formatHubUptime(ms: number): string {
|
|
108
149
|
const totalSeconds = Math.max(0, Math.floor(ms / 1000));
|
|
109
150
|
const days = Math.floor(totalSeconds / 86_400);
|
|
@@ -157,6 +198,10 @@ function cloneSessionMetadata(
|
|
|
157
198
|
if (session.messagesPath?.trim())
|
|
158
199
|
metadata.messagesPath = session.messagesPath;
|
|
159
200
|
if (session.prompt?.trim()) metadata.prompt = session.prompt;
|
|
201
|
+
if (session.provider?.trim()) metadata.provider = session.provider;
|
|
202
|
+
if (session.model?.trim()) metadata.model = session.model;
|
|
203
|
+
if (session.source?.trim()) metadata.source = session.source;
|
|
204
|
+
if (typeof session.pid === "number") metadata.pid = session.pid;
|
|
160
205
|
return Object.keys(metadata).length > 0 ? metadata : undefined;
|
|
161
206
|
}
|
|
162
207
|
|
|
@@ -354,6 +399,14 @@ function formatHubStartupError(
|
|
|
354
399
|
return wrapped;
|
|
355
400
|
}
|
|
356
401
|
|
|
402
|
+
function isAddressInUseError(error: unknown): boolean {
|
|
403
|
+
return (
|
|
404
|
+
error instanceof Error &&
|
|
405
|
+
"code" in error &&
|
|
406
|
+
(error as Error & { code?: string }).code === "EADDRINUSE"
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
|
|
357
410
|
function serializeToolContext(context: ToolContext): Record<string, unknown> {
|
|
358
411
|
return {
|
|
359
412
|
agentId: context.agentId,
|
|
@@ -483,8 +536,13 @@ export class HubServerTransport implements NativeHubTransport {
|
|
|
483
536
|
}) => void;
|
|
484
537
|
}
|
|
485
538
|
>();
|
|
539
|
+
private readonly suppressNextTerminalEventBySession = new Map<
|
|
540
|
+
string,
|
|
541
|
+
string
|
|
542
|
+
>();
|
|
486
543
|
private readonly schedules: HubScheduleService;
|
|
487
544
|
private readonly scheduleCommands: HubScheduleCommandService;
|
|
545
|
+
private readonly cronService?: CronService;
|
|
488
546
|
private readonly sessionHost: RuntimeHost;
|
|
489
547
|
private readonly hubId = createSessionId("hub_");
|
|
490
548
|
private readonly startedAtMs = Date.now();
|
|
@@ -522,27 +580,45 @@ export class HubServerTransport implements NativeHubTransport {
|
|
|
522
580
|
},
|
|
523
581
|
});
|
|
524
582
|
this.scheduleCommands = new HubScheduleCommandService(this.schedules);
|
|
583
|
+
if (options.cronOptions) {
|
|
584
|
+
this.cronService = new CronService({
|
|
585
|
+
runtimeHandlers: options.runtimeHandlers,
|
|
586
|
+
...options.cronOptions,
|
|
587
|
+
});
|
|
588
|
+
}
|
|
525
589
|
this.sessionHost.subscribe((event) => {
|
|
526
|
-
void this.handleSessionEvent(event)
|
|
590
|
+
void this.handleSessionEvent(event).catch((error) => {
|
|
591
|
+
logHubBoundaryError("session event handling failed", error);
|
|
592
|
+
});
|
|
527
593
|
});
|
|
528
594
|
}
|
|
529
595
|
|
|
596
|
+
getCronService(): CronService | undefined {
|
|
597
|
+
return this.cronService;
|
|
598
|
+
}
|
|
599
|
+
|
|
530
600
|
getHubId(): string {
|
|
531
601
|
return this.hubId;
|
|
532
602
|
}
|
|
533
603
|
|
|
534
604
|
async start(): Promise<void> {
|
|
535
605
|
await this.schedules.start();
|
|
606
|
+
if (this.cronService) {
|
|
607
|
+
try {
|
|
608
|
+
await this.cronService.start();
|
|
609
|
+
} catch (err) {
|
|
610
|
+
console.error("[hub] cron service start failed", err);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
536
613
|
}
|
|
537
614
|
|
|
538
615
|
async stop(): Promise<void> {
|
|
539
|
-
for (const
|
|
540
|
-
|
|
616
|
+
for (const approvalId of this.pendingApprovals.keys()) {
|
|
617
|
+
this.resolvePendingApproval(approvalId, {
|
|
541
618
|
approved: false,
|
|
542
619
|
reason: "Hub shutting down before approval was resolved.",
|
|
543
620
|
});
|
|
544
621
|
}
|
|
545
|
-
this.pendingApprovals.clear();
|
|
546
622
|
for (const pending of this.pendingCapabilityRequests.values()) {
|
|
547
623
|
pending.resolve({
|
|
548
624
|
ok: false,
|
|
@@ -552,6 +628,13 @@ export class HubServerTransport implements NativeHubTransport {
|
|
|
552
628
|
this.pendingCapabilityRequests.clear();
|
|
553
629
|
await this.sessionHost.dispose("hub_server_stop");
|
|
554
630
|
await this.schedules.dispose();
|
|
631
|
+
if (this.cronService) {
|
|
632
|
+
try {
|
|
633
|
+
await this.cronService.dispose();
|
|
634
|
+
} catch (err) {
|
|
635
|
+
console.error("[hub] cron service stop failed", err);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
555
638
|
}
|
|
556
639
|
|
|
557
640
|
async handleCommand(envelope: HubCommandEnvelope): Promise<HubReplyEnvelope> {
|
|
@@ -581,10 +664,18 @@ export class HubServerTransport implements NativeHubTransport {
|
|
|
581
664
|
return await this.handleSessionDetach(envelope);
|
|
582
665
|
case "session.get":
|
|
583
666
|
return await this.handleSessionGet(envelope);
|
|
667
|
+
case "session.messages":
|
|
668
|
+
return await this.handleSessionMessages(envelope);
|
|
584
669
|
case "session.list":
|
|
585
670
|
return await this.handleSessionList(envelope);
|
|
586
671
|
case "session.update":
|
|
587
672
|
return await this.handleSessionUpdate(envelope);
|
|
673
|
+
case "session.pending_prompts":
|
|
674
|
+
return await this.handleSessionPendingPrompts(envelope);
|
|
675
|
+
case "session.update_pending_prompt":
|
|
676
|
+
return await this.handleSessionUpdatePendingPrompt(envelope);
|
|
677
|
+
case "session.remove_pending_prompt":
|
|
678
|
+
return await this.handleSessionRemovePendingPrompt(envelope);
|
|
588
679
|
case "session.delete":
|
|
589
680
|
return await this.handleSessionDelete(envelope);
|
|
590
681
|
case "session.hook":
|
|
@@ -748,9 +839,13 @@ export class HubServerTransport implements NativeHubTransport {
|
|
|
748
839
|
sessionId: string,
|
|
749
840
|
clientId: string,
|
|
750
841
|
role: SessionParticipant["role"],
|
|
842
|
+
options: { interactive?: boolean } = {},
|
|
751
843
|
): HubSessionState {
|
|
752
844
|
const existing = this.sessionState.get(sessionId);
|
|
753
845
|
if (existing) {
|
|
846
|
+
if (options.interactive !== undefined) {
|
|
847
|
+
existing.interactive = options.interactive;
|
|
848
|
+
}
|
|
754
849
|
if (!existing.participants.has(clientId)) {
|
|
755
850
|
existing.participants.set(clientId, {
|
|
756
851
|
clientId,
|
|
@@ -762,6 +857,7 @@ export class HubServerTransport implements NativeHubTransport {
|
|
|
762
857
|
}
|
|
763
858
|
const state: HubSessionState = {
|
|
764
859
|
createdByClientId: clientId,
|
|
860
|
+
interactive: options.interactive ?? true,
|
|
765
861
|
participants: new Map([
|
|
766
862
|
[
|
|
767
863
|
clientId,
|
|
@@ -877,6 +973,9 @@ export class HubServerTransport implements NativeHubTransport {
|
|
|
877
973
|
const advertisedToolExecutors = Array.isArray(runtimeOptions.toolExecutors)
|
|
878
974
|
? runtimeOptions.toolExecutors.filter(isHubToolExecutorName)
|
|
879
975
|
: [];
|
|
976
|
+
const configExtensions = parseRuntimeConfigExtensions(
|
|
977
|
+
runtimeOptions.configExtensions,
|
|
978
|
+
);
|
|
880
979
|
const started = await this.sessionHost.start({
|
|
881
980
|
source: typeof metadata.source === "string" ? metadata.source : undefined,
|
|
882
981
|
interactive: metadata.interactive !== false,
|
|
@@ -892,6 +991,7 @@ export class HubServerTransport implements NativeHubTransport {
|
|
|
892
991
|
loadLatestOnInit: true,
|
|
893
992
|
loadPrivateOnAuth: true,
|
|
894
993
|
},
|
|
994
|
+
configExtensions,
|
|
895
995
|
defaultToolExecutors: createCapabilityBackedToolExecutors(
|
|
896
996
|
clientId,
|
|
897
997
|
advertisedToolExecutors,
|
|
@@ -985,7 +1085,9 @@ export class HubServerTransport implements NativeHubTransport {
|
|
|
985
1085
|
? { "*": { autoApprove: true } }
|
|
986
1086
|
: undefined,
|
|
987
1087
|
});
|
|
988
|
-
this.ensureSessionState(started.sessionId, clientId, "creator"
|
|
1088
|
+
this.ensureSessionState(started.sessionId, clientId, "creator", {
|
|
1089
|
+
interactive: metadata.interactive !== false,
|
|
1090
|
+
});
|
|
989
1091
|
const session = await this.readHubSessionRecord(started.sessionId);
|
|
990
1092
|
if (session) {
|
|
991
1093
|
this.publish(
|
|
@@ -1106,6 +1208,45 @@ export class HubServerTransport implements NativeHubTransport {
|
|
|
1106
1208
|
};
|
|
1107
1209
|
}
|
|
1108
1210
|
|
|
1211
|
+
private async handleSessionMessages(
|
|
1212
|
+
envelope: HubCommandEnvelope,
|
|
1213
|
+
): Promise<HubReplyEnvelope> {
|
|
1214
|
+
const sessionId =
|
|
1215
|
+
typeof envelope.payload?.sessionId === "string"
|
|
1216
|
+
? envelope.payload.sessionId.trim()
|
|
1217
|
+
: envelope.sessionId?.trim() || "";
|
|
1218
|
+
if (!sessionId) {
|
|
1219
|
+
return {
|
|
1220
|
+
version: envelope.version,
|
|
1221
|
+
requestId: envelope.requestId,
|
|
1222
|
+
ok: false,
|
|
1223
|
+
error: {
|
|
1224
|
+
code: "invalid_session_id",
|
|
1225
|
+
message: "session.messages requires a session id",
|
|
1226
|
+
},
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
const session = await this.readHubSessionRecord(sessionId);
|
|
1230
|
+
if (!session) {
|
|
1231
|
+
return {
|
|
1232
|
+
version: envelope.version,
|
|
1233
|
+
requestId: envelope.requestId,
|
|
1234
|
+
ok: false,
|
|
1235
|
+
error: {
|
|
1236
|
+
code: "session_not_found",
|
|
1237
|
+
message: `Unknown session: ${sessionId}`,
|
|
1238
|
+
},
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1241
|
+
const messages = await this.sessionHost.readMessages(sessionId);
|
|
1242
|
+
return {
|
|
1243
|
+
version: envelope.version,
|
|
1244
|
+
requestId: envelope.requestId,
|
|
1245
|
+
ok: true,
|
|
1246
|
+
payload: { sessionId, messages },
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1109
1250
|
private async handleSessionList(
|
|
1110
1251
|
envelope: HubCommandEnvelope,
|
|
1111
1252
|
): Promise<HubReplyEnvelope> {
|
|
@@ -1150,6 +1291,81 @@ export class HubServerTransport implements NativeHubTransport {
|
|
|
1150
1291
|
};
|
|
1151
1292
|
}
|
|
1152
1293
|
|
|
1294
|
+
private async handleSessionPendingPrompts(
|
|
1295
|
+
envelope: HubCommandEnvelope,
|
|
1296
|
+
): Promise<HubReplyEnvelope> {
|
|
1297
|
+
const sessionId =
|
|
1298
|
+
typeof envelope.payload?.sessionId === "string"
|
|
1299
|
+
? envelope.payload.sessionId.trim()
|
|
1300
|
+
: envelope.sessionId?.trim() || "";
|
|
1301
|
+
const prompts = await this.sessionHost.pendingPrompts("list", {
|
|
1302
|
+
sessionId,
|
|
1303
|
+
});
|
|
1304
|
+
return {
|
|
1305
|
+
version: envelope.version,
|
|
1306
|
+
requestId: envelope.requestId,
|
|
1307
|
+
ok: true,
|
|
1308
|
+
payload: { sessionId, prompts },
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
private async handleSessionUpdatePendingPrompt(
|
|
1313
|
+
envelope: HubCommandEnvelope,
|
|
1314
|
+
): Promise<HubReplyEnvelope> {
|
|
1315
|
+
const sessionId =
|
|
1316
|
+
typeof envelope.payload?.sessionId === "string"
|
|
1317
|
+
? envelope.payload.sessionId.trim()
|
|
1318
|
+
: envelope.sessionId?.trim() || "";
|
|
1319
|
+
const promptId =
|
|
1320
|
+
typeof envelope.payload?.promptId === "string"
|
|
1321
|
+
? envelope.payload.promptId.trim()
|
|
1322
|
+
: "";
|
|
1323
|
+
const prompt =
|
|
1324
|
+
typeof envelope.payload?.prompt === "string"
|
|
1325
|
+
? envelope.payload.prompt
|
|
1326
|
+
: undefined;
|
|
1327
|
+
const delivery =
|
|
1328
|
+
envelope.payload?.delivery === "queue" ||
|
|
1329
|
+
envelope.payload?.delivery === "steer"
|
|
1330
|
+
? envelope.payload.delivery
|
|
1331
|
+
: undefined;
|
|
1332
|
+
const result = await this.sessionHost.pendingPrompts("update", {
|
|
1333
|
+
sessionId,
|
|
1334
|
+
promptId,
|
|
1335
|
+
prompt,
|
|
1336
|
+
delivery,
|
|
1337
|
+
});
|
|
1338
|
+
return {
|
|
1339
|
+
version: envelope.version,
|
|
1340
|
+
requestId: envelope.requestId,
|
|
1341
|
+
ok: true,
|
|
1342
|
+
payload: result as unknown as Record<string, JsonValue | undefined>,
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
private async handleSessionRemovePendingPrompt(
|
|
1347
|
+
envelope: HubCommandEnvelope,
|
|
1348
|
+
): Promise<HubReplyEnvelope> {
|
|
1349
|
+
const sessionId =
|
|
1350
|
+
typeof envelope.payload?.sessionId === "string"
|
|
1351
|
+
? envelope.payload.sessionId.trim()
|
|
1352
|
+
: envelope.sessionId?.trim() || "";
|
|
1353
|
+
const promptId =
|
|
1354
|
+
typeof envelope.payload?.promptId === "string"
|
|
1355
|
+
? envelope.payload.promptId.trim()
|
|
1356
|
+
: "";
|
|
1357
|
+
const result = await this.sessionHost.pendingPrompts("delete", {
|
|
1358
|
+
sessionId,
|
|
1359
|
+
promptId,
|
|
1360
|
+
});
|
|
1361
|
+
return {
|
|
1362
|
+
version: envelope.version,
|
|
1363
|
+
requestId: envelope.requestId,
|
|
1364
|
+
ok: true,
|
|
1365
|
+
payload: result as unknown as Record<string, JsonValue | undefined>,
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1153
1369
|
private async handleSessionDelete(
|
|
1154
1370
|
envelope: HubCommandEnvelope,
|
|
1155
1371
|
): Promise<HubReplyEnvelope> {
|
|
@@ -1202,6 +1418,9 @@ export class HubServerTransport implements NativeHubTransport {
|
|
|
1202
1418
|
!Array.isArray(payload.attachments)
|
|
1203
1419
|
? (payload.attachments as Record<string, unknown>)
|
|
1204
1420
|
: undefined;
|
|
1421
|
+
const userFiles = Array.isArray(attachments?.userFiles)
|
|
1422
|
+
? attachments.userFiles.filter((filePath) => typeof filePath === "string")
|
|
1423
|
+
: undefined;
|
|
1205
1424
|
const result = await this.sessionHost.send({
|
|
1206
1425
|
sessionId,
|
|
1207
1426
|
prompt,
|
|
@@ -1212,8 +1431,13 @@ export class HubServerTransport implements NativeHubTransport {
|
|
|
1212
1431
|
userImages: Array.isArray(attachments?.userImages)
|
|
1213
1432
|
? (attachments.userImages as string[])
|
|
1214
1433
|
: undefined,
|
|
1434
|
+
userFiles,
|
|
1215
1435
|
});
|
|
1216
1436
|
if (result) {
|
|
1437
|
+
this.suppressNextTerminalEventBySession.set(
|
|
1438
|
+
sessionId,
|
|
1439
|
+
result.finishReason,
|
|
1440
|
+
);
|
|
1217
1441
|
this.publish(
|
|
1218
1442
|
this.buildEvent(
|
|
1219
1443
|
"run.completed",
|
|
@@ -1283,9 +1507,18 @@ export class HubServerTransport implements NativeHubTransport {
|
|
|
1283
1507
|
request: ToolApprovalRequest,
|
|
1284
1508
|
): Promise<{ approved: boolean; reason?: string }> {
|
|
1285
1509
|
const approvalId = createSessionId("approval_");
|
|
1510
|
+
const sessionId = request.sessionId;
|
|
1511
|
+
const state = this.sessionState.get(sessionId);
|
|
1512
|
+
if (state?.interactive === false) {
|
|
1513
|
+
return {
|
|
1514
|
+
approved: false,
|
|
1515
|
+
reason:
|
|
1516
|
+
"Tool approval requires an interactive session, but this session is non-interactive.",
|
|
1517
|
+
};
|
|
1518
|
+
}
|
|
1286
1519
|
return await new Promise((resolve) => {
|
|
1287
1520
|
this.pendingApprovals.set(approvalId, {
|
|
1288
|
-
sessionId
|
|
1521
|
+
sessionId,
|
|
1289
1522
|
resolve,
|
|
1290
1523
|
});
|
|
1291
1524
|
this.publish(
|
|
@@ -1293,16 +1526,34 @@ export class HubServerTransport implements NativeHubTransport {
|
|
|
1293
1526
|
"approval.requested",
|
|
1294
1527
|
{
|
|
1295
1528
|
approvalId,
|
|
1529
|
+
sessionId: request.sessionId,
|
|
1530
|
+
agentId: request.agentId,
|
|
1531
|
+
conversationId: request.conversationId,
|
|
1532
|
+
iteration: request.iteration,
|
|
1296
1533
|
toolCallId: request.toolCallId,
|
|
1297
1534
|
toolName: request.toolName,
|
|
1298
1535
|
inputJson: JSON.stringify(request.input ?? null),
|
|
1536
|
+
policy: request.policy,
|
|
1299
1537
|
},
|
|
1300
|
-
|
|
1538
|
+
sessionId,
|
|
1301
1539
|
),
|
|
1302
1540
|
);
|
|
1303
1541
|
});
|
|
1304
1542
|
}
|
|
1305
1543
|
|
|
1544
|
+
private resolvePendingApproval(
|
|
1545
|
+
approvalId: string,
|
|
1546
|
+
result: { approved: boolean; reason?: string },
|
|
1547
|
+
): { sessionId: string } | undefined {
|
|
1548
|
+
const pending = this.pendingApprovals.get(approvalId);
|
|
1549
|
+
if (!pending) {
|
|
1550
|
+
return undefined;
|
|
1551
|
+
}
|
|
1552
|
+
this.pendingApprovals.delete(approvalId);
|
|
1553
|
+
pending.resolve(result);
|
|
1554
|
+
return { sessionId: pending.sessionId };
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1306
1557
|
private async handleApprovalRespond(
|
|
1307
1558
|
envelope: HubCommandEnvelope,
|
|
1308
1559
|
): Promise<HubReplyEnvelope> {
|
|
@@ -1322,25 +1573,37 @@ export class HubServerTransport implements NativeHubTransport {
|
|
|
1322
1573
|
},
|
|
1323
1574
|
};
|
|
1324
1575
|
}
|
|
1325
|
-
this.pendingApprovals.delete(approvalId);
|
|
1326
1576
|
const reason =
|
|
1327
|
-
envelope.payload?.
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1577
|
+
typeof envelope.payload?.reason === "string"
|
|
1578
|
+
? envelope.payload.reason
|
|
1579
|
+
: envelope.payload?.payload &&
|
|
1580
|
+
typeof envelope.payload.payload === "object" &&
|
|
1581
|
+
!Array.isArray(envelope.payload.payload) &&
|
|
1582
|
+
typeof (envelope.payload.payload as Record<string, unknown>)
|
|
1583
|
+
.reason === "string"
|
|
1584
|
+
? ((envelope.payload.payload as Record<string, unknown>)
|
|
1585
|
+
.reason as string)
|
|
1586
|
+
: undefined;
|
|
1587
|
+
const resolved = this.resolvePendingApproval(approvalId, {
|
|
1336
1588
|
approved: envelope.payload?.approved === true,
|
|
1337
1589
|
reason,
|
|
1338
1590
|
});
|
|
1591
|
+
if (!resolved) {
|
|
1592
|
+
return {
|
|
1593
|
+
version: envelope.version,
|
|
1594
|
+
requestId: envelope.requestId,
|
|
1595
|
+
ok: false,
|
|
1596
|
+
error: {
|
|
1597
|
+
code: "approval_not_found",
|
|
1598
|
+
message: `Unknown approval: ${approvalId}`,
|
|
1599
|
+
},
|
|
1600
|
+
};
|
|
1601
|
+
}
|
|
1339
1602
|
this.publish(
|
|
1340
1603
|
this.buildEvent(
|
|
1341
1604
|
"approval.resolved",
|
|
1342
1605
|
{ approvalId, approved: envelope.payload?.approved === true, reason },
|
|
1343
|
-
|
|
1606
|
+
resolved.sessionId,
|
|
1344
1607
|
),
|
|
1345
1608
|
);
|
|
1346
1609
|
return {
|
|
@@ -1473,6 +1736,30 @@ export class HubServerTransport implements NativeHubTransport {
|
|
|
1473
1736
|
return;
|
|
1474
1737
|
case "agent_event": {
|
|
1475
1738
|
const { sessionId, event: agentEvent } = event.payload;
|
|
1739
|
+
if (agentEvent.type === "iteration_start") {
|
|
1740
|
+
this.publish(
|
|
1741
|
+
this.buildEvent(
|
|
1742
|
+
"iteration.started",
|
|
1743
|
+
{ iteration: agentEvent.iteration },
|
|
1744
|
+
sessionId,
|
|
1745
|
+
),
|
|
1746
|
+
);
|
|
1747
|
+
return;
|
|
1748
|
+
}
|
|
1749
|
+
if (agentEvent.type === "iteration_end") {
|
|
1750
|
+
this.publish(
|
|
1751
|
+
this.buildEvent(
|
|
1752
|
+
"iteration.finished",
|
|
1753
|
+
{
|
|
1754
|
+
iteration: agentEvent.iteration,
|
|
1755
|
+
hadToolCalls: agentEvent.hadToolCalls,
|
|
1756
|
+
toolCallCount: agentEvent.toolCallCount,
|
|
1757
|
+
},
|
|
1758
|
+
sessionId,
|
|
1759
|
+
),
|
|
1760
|
+
);
|
|
1761
|
+
return;
|
|
1762
|
+
}
|
|
1476
1763
|
if (agentEvent.type === "content_start") {
|
|
1477
1764
|
if (
|
|
1478
1765
|
agentEvent.contentType === "text" &&
|
|
@@ -1531,18 +1818,52 @@ export class HubServerTransport implements NativeHubTransport {
|
|
|
1531
1818
|
return;
|
|
1532
1819
|
}
|
|
1533
1820
|
}
|
|
1534
|
-
if (
|
|
1535
|
-
agentEvent.
|
|
1536
|
-
|
|
1537
|
-
|
|
1821
|
+
if (agentEvent.type === "content_end") {
|
|
1822
|
+
switch (agentEvent.contentType) {
|
|
1823
|
+
case "text":
|
|
1824
|
+
this.publish(
|
|
1825
|
+
this.buildEvent(
|
|
1826
|
+
"assistant.finished",
|
|
1827
|
+
{ text: agentEvent.text },
|
|
1828
|
+
sessionId,
|
|
1829
|
+
),
|
|
1830
|
+
);
|
|
1831
|
+
break;
|
|
1832
|
+
case "reasoning":
|
|
1833
|
+
this.publish(
|
|
1834
|
+
this.buildEvent(
|
|
1835
|
+
"reasoning.finished",
|
|
1836
|
+
{ reasoning: agentEvent.reasoning },
|
|
1837
|
+
sessionId,
|
|
1838
|
+
),
|
|
1839
|
+
);
|
|
1840
|
+
break;
|
|
1841
|
+
case "tool":
|
|
1842
|
+
this.publish(
|
|
1843
|
+
this.buildEvent(
|
|
1844
|
+
"tool.finished",
|
|
1845
|
+
{
|
|
1846
|
+
toolCallId: agentEvent.toolCallId,
|
|
1847
|
+
toolName: agentEvent.toolName,
|
|
1848
|
+
output: agentEvent.output,
|
|
1849
|
+
error: agentEvent.error,
|
|
1850
|
+
},
|
|
1851
|
+
sessionId,
|
|
1852
|
+
),
|
|
1853
|
+
);
|
|
1854
|
+
break;
|
|
1855
|
+
}
|
|
1856
|
+
return;
|
|
1857
|
+
}
|
|
1858
|
+
if (agentEvent.type === "done") {
|
|
1538
1859
|
this.publish(
|
|
1539
1860
|
this.buildEvent(
|
|
1540
|
-
"
|
|
1861
|
+
"agent.done",
|
|
1541
1862
|
{
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1863
|
+
reason: agentEvent.reason,
|
|
1864
|
+
text: agentEvent.text,
|
|
1865
|
+
iterations: agentEvent.iterations,
|
|
1866
|
+
usage: agentEvent.usage,
|
|
1546
1867
|
},
|
|
1547
1868
|
sessionId,
|
|
1548
1869
|
),
|
|
@@ -1586,6 +1907,35 @@ export class HubServerTransport implements NativeHubTransport {
|
|
|
1586
1907
|
);
|
|
1587
1908
|
return;
|
|
1588
1909
|
}
|
|
1910
|
+
case "pending_prompts": {
|
|
1911
|
+
this.publish(
|
|
1912
|
+
this.buildEvent(
|
|
1913
|
+
"session.pending_prompts",
|
|
1914
|
+
{
|
|
1915
|
+
sessionId: event.payload.sessionId,
|
|
1916
|
+
prompts: event.payload.prompts,
|
|
1917
|
+
},
|
|
1918
|
+
event.payload.sessionId,
|
|
1919
|
+
),
|
|
1920
|
+
);
|
|
1921
|
+
return;
|
|
1922
|
+
}
|
|
1923
|
+
case "pending_prompt_submitted": {
|
|
1924
|
+
const prompt: SessionPendingPrompt = {
|
|
1925
|
+
id: event.payload.id,
|
|
1926
|
+
prompt: event.payload.prompt,
|
|
1927
|
+
delivery: event.payload.delivery,
|
|
1928
|
+
attachmentCount: event.payload.attachmentCount,
|
|
1929
|
+
};
|
|
1930
|
+
this.publish(
|
|
1931
|
+
this.buildEvent(
|
|
1932
|
+
"session.pending_prompt_submitted",
|
|
1933
|
+
{ sessionId: event.payload.sessionId, prompt },
|
|
1934
|
+
event.payload.sessionId,
|
|
1935
|
+
),
|
|
1936
|
+
);
|
|
1937
|
+
return;
|
|
1938
|
+
}
|
|
1589
1939
|
case "status": {
|
|
1590
1940
|
const session = await this.readHubSessionRecord(
|
|
1591
1941
|
event.payload.sessionId,
|
|
@@ -1601,7 +1951,16 @@ export class HubServerTransport implements NativeHubTransport {
|
|
|
1601
1951
|
}
|
|
1602
1952
|
return;
|
|
1603
1953
|
}
|
|
1604
|
-
case "ended":
|
|
1954
|
+
case "ended": {
|
|
1955
|
+
const suppressDuplicateTerminalEvent =
|
|
1956
|
+
this.suppressNextTerminalEventBySession.get(
|
|
1957
|
+
event.payload.sessionId,
|
|
1958
|
+
) === event.payload.reason;
|
|
1959
|
+
if (suppressDuplicateTerminalEvent) {
|
|
1960
|
+
this.suppressNextTerminalEventBySession.delete(
|
|
1961
|
+
event.payload.sessionId,
|
|
1962
|
+
);
|
|
1963
|
+
}
|
|
1605
1964
|
if (event.payload.reason === "completed") {
|
|
1606
1965
|
const session = await this.readHubSessionRecord(
|
|
1607
1966
|
event.payload.sessionId,
|
|
@@ -1611,6 +1970,9 @@ export class HubServerTransport implements NativeHubTransport {
|
|
|
1611
1970
|
this.buildEvent("ui.notify", notification, event.payload.sessionId),
|
|
1612
1971
|
);
|
|
1613
1972
|
}
|
|
1973
|
+
if (suppressDuplicateTerminalEvent) {
|
|
1974
|
+
return;
|
|
1975
|
+
}
|
|
1614
1976
|
this.publish(
|
|
1615
1977
|
this.buildEvent(
|
|
1616
1978
|
event.payload.reason === "aborted"
|
|
@@ -1621,6 +1983,7 @@ export class HubServerTransport implements NativeHubTransport {
|
|
|
1621
1983
|
),
|
|
1622
1984
|
);
|
|
1623
1985
|
return;
|
|
1986
|
+
}
|
|
1624
1987
|
default:
|
|
1625
1988
|
return;
|
|
1626
1989
|
}
|
|
@@ -1653,12 +2016,25 @@ export class HubServerTransport implements NativeHubTransport {
|
|
|
1653
2016
|
if (entry.sessionId && entry.sessionId !== event.sessionId) {
|
|
1654
2017
|
continue;
|
|
1655
2018
|
}
|
|
1656
|
-
|
|
2019
|
+
try {
|
|
2020
|
+
entry.listener(event);
|
|
2021
|
+
} catch (error) {
|
|
2022
|
+
logHubBoundaryError(
|
|
2023
|
+
`listener threw while publishing ${event.event}`,
|
|
2024
|
+
error,
|
|
2025
|
+
);
|
|
2026
|
+
}
|
|
1657
2027
|
}
|
|
1658
2028
|
}
|
|
1659
2029
|
}
|
|
1660
2030
|
}
|
|
1661
2031
|
|
|
2032
|
+
function logHubBoundaryError(message: string, error: unknown): void {
|
|
2033
|
+
const details =
|
|
2034
|
+
error instanceof Error ? error.stack || error.message : String(error);
|
|
2035
|
+
console.error(`[hub] ${message}: ${details}`);
|
|
2036
|
+
}
|
|
2037
|
+
|
|
1662
2038
|
export interface HubWebSocketServerOptions {
|
|
1663
2039
|
host?: string;
|
|
1664
2040
|
port?: number;
|
|
@@ -1667,6 +2043,14 @@ export interface HubWebSocketServerOptions {
|
|
|
1667
2043
|
sessionHost?: RuntimeHost;
|
|
1668
2044
|
runtimeHandlers: HubScheduleRuntimeHandlers;
|
|
1669
2045
|
scheduleOptions?: Omit<HubScheduleServiceOptions, "runtimeHandlers">;
|
|
2046
|
+
/**
|
|
2047
|
+
* File-based cron automation options. When provided, the hub starts a
|
|
2048
|
+
* `CronService` that watches global `~/.cline/cron/` by default, reconciles
|
|
2049
|
+
* specs into `cron.db`, and executes queued runs through `runtimeHandlers`.
|
|
2050
|
+
* Pass `cronOptions.specs` to use a different source, including future
|
|
2051
|
+
* workspace-scoped specs.
|
|
2052
|
+
*/
|
|
2053
|
+
cronOptions?: Omit<CronServiceOptions, "runtimeHandlers">;
|
|
1670
2054
|
/**
|
|
1671
2055
|
* Custom `fetch` implementation forwarded to the internally-constructed
|
|
1672
2056
|
* `LocalRuntimeHost` that executes incoming `session.create` traffic.
|
|
@@ -1687,7 +2071,9 @@ export interface HubWebSocketServer {
|
|
|
1687
2071
|
}
|
|
1688
2072
|
|
|
1689
2073
|
export interface EnsureHubWebSocketServerOptions
|
|
1690
|
-
extends HubWebSocketServerOptions {
|
|
2074
|
+
extends HubWebSocketServerOptions {
|
|
2075
|
+
allowPortFallback?: boolean;
|
|
2076
|
+
}
|
|
1691
2077
|
|
|
1692
2078
|
export interface EnsuredHubWebSocketServerResult {
|
|
1693
2079
|
server?: HubWebSocketServer;
|
|
@@ -1703,7 +2089,7 @@ export async function startHubWebSocketServer(
|
|
|
1703
2089
|
const owner = options.owner ?? resolveHubOwnerContext();
|
|
1704
2090
|
const host = options.host ?? "127.0.0.1";
|
|
1705
2091
|
const pathname = options.pathname ?? "/hub";
|
|
1706
|
-
const requestedPort = options.port ??
|
|
2092
|
+
const requestedPort = options.port ?? resolveDefaultHubPort();
|
|
1707
2093
|
let port = requestedPort;
|
|
1708
2094
|
let url = createHubServerUrl(host, requestedPort, pathname);
|
|
1709
2095
|
const buildId = resolveHubBuildId();
|
|
@@ -1720,6 +2106,43 @@ export async function startHubWebSocketServer(
|
|
|
1720
2106
|
pid: process.pid,
|
|
1721
2107
|
startedAt,
|
|
1722
2108
|
} as const;
|
|
2109
|
+
let closePromise: Promise<void> | undefined;
|
|
2110
|
+
|
|
2111
|
+
const closeServer = async (): Promise<void> => {
|
|
2112
|
+
if (closePromise) {
|
|
2113
|
+
return closePromise;
|
|
2114
|
+
}
|
|
2115
|
+
closePromise = (async () => {
|
|
2116
|
+
for (const detach of cleanup) {
|
|
2117
|
+
detach();
|
|
2118
|
+
}
|
|
2119
|
+
cleanup.clear();
|
|
2120
|
+
await new Promise<void>((resolve, reject) => {
|
|
2121
|
+
wss.close((error?: Error) => {
|
|
2122
|
+
if (error) {
|
|
2123
|
+
reject(error);
|
|
2124
|
+
return;
|
|
2125
|
+
}
|
|
2126
|
+
resolve();
|
|
2127
|
+
});
|
|
2128
|
+
});
|
|
2129
|
+
await new Promise<void>((resolve, reject) => {
|
|
2130
|
+
server.close((error) => {
|
|
2131
|
+
if (error) {
|
|
2132
|
+
reject(error);
|
|
2133
|
+
return;
|
|
2134
|
+
}
|
|
2135
|
+
resolve();
|
|
2136
|
+
});
|
|
2137
|
+
});
|
|
2138
|
+
await transport.stop();
|
|
2139
|
+
const current = await readHubDiscovery(owner.discoveryPath);
|
|
2140
|
+
if (current?.url === url) {
|
|
2141
|
+
await clearHubDiscovery(owner.discoveryPath);
|
|
2142
|
+
}
|
|
2143
|
+
})();
|
|
2144
|
+
return closePromise;
|
|
2145
|
+
};
|
|
1723
2146
|
|
|
1724
2147
|
const server = http.createServer((req, res) => {
|
|
1725
2148
|
if ((req.url ?? "/") === "/health") {
|
|
@@ -1742,6 +2165,15 @@ export async function startHubWebSocketServer(
|
|
|
1742
2165
|
res.end(JSON.stringify(versionPayload));
|
|
1743
2166
|
return;
|
|
1744
2167
|
}
|
|
2168
|
+
if ((req.url ?? "/") === "/shutdown" && req.method === "POST") {
|
|
2169
|
+
res.statusCode = 202;
|
|
2170
|
+
res.setHeader("content-type", "application/json");
|
|
2171
|
+
res.end(JSON.stringify({ ok: true }));
|
|
2172
|
+
queueMicrotask(() => {
|
|
2173
|
+
void closeServer();
|
|
2174
|
+
});
|
|
2175
|
+
return;
|
|
2176
|
+
}
|
|
1745
2177
|
res.statusCode = 404;
|
|
1746
2178
|
res.end("Not found");
|
|
1747
2179
|
});
|
|
@@ -1753,14 +2185,23 @@ export async function startHubWebSocketServer(
|
|
|
1753
2185
|
socket.destroy();
|
|
1754
2186
|
return;
|
|
1755
2187
|
}
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
2188
|
+
try {
|
|
2189
|
+
wss.handleUpgrade(
|
|
2190
|
+
request,
|
|
2191
|
+
socket,
|
|
2192
|
+
head,
|
|
2193
|
+
(websocket: NodeWebSocketLike) => {
|
|
2194
|
+
const detach = adapter.attach(wrapWsSocket(websocket));
|
|
2195
|
+
cleanup.add(detach);
|
|
2196
|
+
websocket.once("close", () => {
|
|
2197
|
+
detach();
|
|
2198
|
+
cleanup.delete(detach);
|
|
2199
|
+
});
|
|
2200
|
+
},
|
|
2201
|
+
);
|
|
2202
|
+
} catch {
|
|
2203
|
+
rejectUpgradeSocket(socket);
|
|
2204
|
+
}
|
|
1764
2205
|
});
|
|
1765
2206
|
|
|
1766
2207
|
await new Promise<void>((resolve, reject) => {
|
|
@@ -1807,35 +2248,7 @@ export async function startHubWebSocketServer(
|
|
|
1807
2248
|
host,
|
|
1808
2249
|
port,
|
|
1809
2250
|
url,
|
|
1810
|
-
close:
|
|
1811
|
-
for (const detach of cleanup) {
|
|
1812
|
-
detach();
|
|
1813
|
-
}
|
|
1814
|
-
cleanup.clear();
|
|
1815
|
-
await new Promise<void>((resolve, reject) => {
|
|
1816
|
-
wss.close((error?: Error) => {
|
|
1817
|
-
if (error) {
|
|
1818
|
-
reject(error);
|
|
1819
|
-
return;
|
|
1820
|
-
}
|
|
1821
|
-
resolve();
|
|
1822
|
-
});
|
|
1823
|
-
});
|
|
1824
|
-
await new Promise<void>((resolve, reject) => {
|
|
1825
|
-
server.close((error) => {
|
|
1826
|
-
if (error) {
|
|
1827
|
-
reject(error);
|
|
1828
|
-
return;
|
|
1829
|
-
}
|
|
1830
|
-
resolve();
|
|
1831
|
-
});
|
|
1832
|
-
});
|
|
1833
|
-
await transport.stop();
|
|
1834
|
-
const current = await readHubDiscovery(owner.discoveryPath);
|
|
1835
|
-
if (current?.url === url) {
|
|
1836
|
-
await clearHubDiscovery(owner.discoveryPath);
|
|
1837
|
-
}
|
|
1838
|
-
},
|
|
2251
|
+
close: closeServer,
|
|
1839
2252
|
};
|
|
1840
2253
|
}
|
|
1841
2254
|
|
|
@@ -1844,7 +2257,7 @@ export async function ensureHubWebSocketServer(
|
|
|
1844
2257
|
): Promise<EnsuredHubWebSocketServerResult> {
|
|
1845
2258
|
const owner = options.owner ?? resolveHubOwnerContext();
|
|
1846
2259
|
const host = options.host ?? "127.0.0.1";
|
|
1847
|
-
const port = options.port ??
|
|
2260
|
+
const port = options.port ?? resolveDefaultHubPort();
|
|
1848
2261
|
const pathname = options.pathname ?? "/hub";
|
|
1849
2262
|
const expectedUrl = createHubServerUrl(host, port, pathname);
|
|
1850
2263
|
const sharedKey = owner.discoveryPath;
|
|
@@ -1858,7 +2271,10 @@ export async function ensureHubWebSocketServer(
|
|
|
1858
2271
|
|
|
1859
2272
|
return await withHubStartupLock(owner.discoveryPath, async () => {
|
|
1860
2273
|
const discovered = await readHubDiscovery(owner.discoveryPath);
|
|
1861
|
-
|
|
2274
|
+
const canReuseDiscovered =
|
|
2275
|
+
discovered?.url &&
|
|
2276
|
+
(discovered.url === expectedUrl || options.allowPortFallback === true);
|
|
2277
|
+
if (canReuseDiscovered) {
|
|
1862
2278
|
const healthy = await probeHubServer(discovered.url);
|
|
1863
2279
|
if (healthy?.url && (await verifyHubConnection(healthy.url))) {
|
|
1864
2280
|
return { url: healthy.url, action: "reuse" };
|
|
@@ -1875,14 +2291,27 @@ export async function ensureHubWebSocketServer(
|
|
|
1875
2291
|
await clearHubDiscovery(owner.discoveryPath);
|
|
1876
2292
|
}
|
|
1877
2293
|
|
|
1878
|
-
const
|
|
1879
|
-
|
|
2294
|
+
const start = async (
|
|
2295
|
+
startOptions: HubWebSocketServerOptions,
|
|
2296
|
+
): Promise<EnsuredHubWebSocketServerResult> => {
|
|
2297
|
+
const serverPromise = startHubWebSocketServer({ ...startOptions, owner });
|
|
2298
|
+
SHARED_SERVERS.set(sharedKey, serverPromise);
|
|
2299
|
+
try {
|
|
2300
|
+
const server = await serverPromise;
|
|
2301
|
+
return { server, url: server.url, action: "started" };
|
|
2302
|
+
} catch (error) {
|
|
2303
|
+
SHARED_SERVERS.delete(sharedKey);
|
|
2304
|
+
throw error;
|
|
2305
|
+
}
|
|
2306
|
+
};
|
|
2307
|
+
|
|
1880
2308
|
try {
|
|
1881
|
-
|
|
1882
|
-
return { server, url: server.url, action: "started" };
|
|
2309
|
+
return await start(options);
|
|
1883
2310
|
} catch (error) {
|
|
1884
|
-
|
|
1885
|
-
|
|
2311
|
+
if (!options.allowPortFallback || !isAddressInUseError(error)) {
|
|
2312
|
+
throw error;
|
|
2313
|
+
}
|
|
2314
|
+
return await start({ ...options, port: 0 });
|
|
1886
2315
|
}
|
|
1887
2316
|
});
|
|
1888
2317
|
}
|