@gajae-code/coding-agent 0.12.11 → 0.12.13
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 +68 -0
- package/dist/types/cli/marketplace-hint.d.ts +28 -0
- package/dist/types/cli/notify-cli.d.ts +42 -1
- package/dist/types/cli/update-cli.d.ts +9 -3
- package/dist/types/cli.d.ts +10 -0
- package/dist/types/commands/notify.d.ts +6 -0
- package/dist/types/config/file-lock-gc.d.ts +13 -1
- package/dist/types/config/file-lock.d.ts +3 -0
- package/dist/types/config/model-registry.d.ts +6 -0
- package/dist/types/config/models-config-schema.d.ts +99 -0
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/coordinator-mcp/question-state.d.ts +3 -1
- package/dist/types/extensibility/extensions/index.d.ts +1 -0
- package/dist/types/extensibility/extensions/ouroboros-ooo-bridge.d.ts +13 -0
- package/dist/types/extensibility/extensions/prefix-command-bridge.d.ts +0 -1
- package/dist/types/extensibility/extensions/types.d.ts +2 -0
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +14 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +11 -0
- package/dist/types/main.d.ts +6 -2
- package/dist/types/modes/acp/acp-agent.d.ts +24 -1
- package/dist/types/modes/components/session-selector.d.ts +1 -1
- package/dist/types/modes/utils/host-status.d.ts +23 -0
- package/dist/types/runtime-mcp/types.d.ts +2 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +12 -0
- package/dist/types/sdk/bus/chat-daemon-cli.d.ts +3 -0
- package/dist/types/sdk/bus/chat-daemon-command-channel.d.ts +244 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +11 -1
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +7 -0
- package/dist/types/sdk/bus/control-plane-frames.d.ts +43 -0
- package/dist/types/sdk/bus/conversation-store.d.ts +18 -0
- package/dist/types/sdk/bus/existing-thread-readiness.d.ts +71 -0
- package/dist/types/sdk/bus/html-format.d.ts +10 -0
- package/dist/types/sdk/bus/notification-service.d.ts +10 -0
- package/dist/types/sdk/bus/slack-daemon.d.ts +27 -0
- package/dist/types/sdk/bus/slack-live-provider.d.ts +11 -0
- package/dist/types/sdk/bus/slack-provider.d.ts +14 -0
- package/dist/types/sdk/bus/slack-thread-binding.d.ts +140 -0
- package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +2 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +28 -7
- package/dist/types/sdk/bus/telegram-daemon.d.ts +119 -17
- package/dist/types/sdk/bus/telegram-reference.d.ts +20 -1
- package/dist/types/sdk/bus/topic-registry.d.ts +130 -85
- package/dist/types/sdk/host/host.d.ts +52 -0
- package/dist/types/sdk/lifecycle-session.d.ts +6 -0
- package/dist/types/sdk/session-activation.d.ts +41 -0
- package/dist/types/sdk/session.d.ts +18 -0
- package/dist/types/sdk/startup-capability.d.ts +8 -1
- package/dist/types/session/agent-session.d.ts +7 -2
- package/dist/types/session/artifacts.d.ts +2 -2
- package/dist/types/session/session-manager.d.ts +40 -6
- package/dist/types/tools/index.d.ts +15 -0
- package/dist/types/utils/npm-registry.d.ts +71 -0
- package/examples/extensions/README.md +38 -4
- package/examples/extensions/ooo-bridge.ts +15 -0
- package/package.json +8 -8
- package/scripts/generate-sdk-operation-inventory.ts +4 -0
- package/src/async/job-manager.ts +9 -2
- package/src/cli/fast-help.ts +1 -0
- package/src/cli/marketplace-hint.ts +39 -0
- package/src/cli/notify-cli.ts +226 -2
- package/src/cli/plugin-cli.ts +10 -0
- package/src/cli/update-cli.ts +90 -23
- package/src/cli.ts +23 -0
- package/src/commands/notify.ts +27 -3
- package/src/commands/sdk.ts +1 -0
- package/src/config/file-lock-gc.ts +65 -26
- package/src/config/file-lock.ts +61 -16
- package/src/config/model-profiles.ts +46 -30
- package/src/config/model-registry.ts +17 -5
- package/src/config/models-config-schema.ts +9 -4
- package/src/coordinator/contract.ts +1 -0
- package/src/coordinator-mcp/question-state.ts +2 -0
- package/src/coordinator-mcp/server.ts +358 -7
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +2 -0
- package/src/extensibility/extensions/index.ts +1 -0
- package/src/extensibility/extensions/ouroboros-ooo-bridge.ts +235 -0
- package/src/extensibility/extensions/prefix-command-bridge.ts +4 -9
- package/src/extensibility/extensions/runner.ts +15 -2
- package/src/extensibility/extensions/types.ts +2 -0
- package/src/gjc-runtime/deep-interview-recorder.ts +9 -5
- package/src/gjc-runtime/deep-interview-state.ts +27 -3
- package/src/gjc-runtime/gc-render.ts +8 -1
- package/src/gjc-runtime/gc-runtime.ts +15 -1
- package/src/gjc-runtime/launch-tmux.ts +53 -1
- package/src/internal-urls/docs-index.generated.ts +14 -13
- package/src/main.ts +214 -70
- package/src/modes/DESIGN.md +8 -5
- package/src/modes/acp/acp-agent.ts +123 -7
- package/src/modes/components/footer.ts +10 -18
- package/src/modes/components/model-selector.ts +52 -5
- package/src/modes/components/session-selector.ts +26 -3
- package/src/modes/components/status-line/gh.ts +23 -2
- package/src/modes/controllers/event-controller.ts +3 -0
- package/src/modes/controllers/extension-ui-controller.ts +2 -0
- package/src/modes/controllers/input-controller.ts +11 -0
- package/src/modes/controllers/selector-controller.ts +8 -2
- package/src/modes/interactive-mode.ts +7 -1
- package/src/modes/print-mode.ts +10 -0
- package/src/modes/utils/host-status.ts +38 -0
- package/src/prompts/system/project-prompt.md +19 -0
- package/src/runtime-mcp/types.ts +2 -0
- package/src/sdk/broker/lifecycle.ts +60 -3
- package/src/sdk/bus/chat-daemon-cli.ts +42 -0
- package/src/sdk/bus/chat-daemon-command-channel.ts +921 -0
- package/src/sdk/bus/chat-daemon-control.ts +16 -3
- package/src/sdk/bus/chat-daemon-runtime.ts +257 -21
- package/src/sdk/bus/control-plane-frames.ts +51 -0
- package/src/sdk/bus/conversation-store.ts +29 -1
- package/src/sdk/bus/existing-thread-readiness.ts +155 -0
- package/src/sdk/bus/html-format.ts +19 -3
- package/src/sdk/bus/index.ts +91 -12
- package/src/sdk/bus/notification-service.ts +28 -1
- package/src/sdk/bus/rich-render.ts +3 -2
- package/src/sdk/bus/slack-daemon.ts +99 -4
- package/src/sdk/bus/slack-live-provider.ts +32 -2
- package/src/sdk/bus/slack-provider.ts +11 -0
- package/src/sdk/bus/slack-thread-binding.ts +641 -0
- package/src/sdk/bus/telegram-daemon-cli.ts +11 -0
- package/src/sdk/bus/telegram-daemon-contract.ts +28 -7
- package/src/sdk/bus/telegram-daemon.ts +2007 -509
- package/src/sdk/bus/telegram-reference.ts +114 -15
- package/src/sdk/bus/topic-registry.ts +674 -181
- package/src/sdk/host/host.ts +180 -1
- package/src/sdk/lifecycle-session.ts +8 -2
- package/src/sdk/protocol/operation-inventory.generated.json +22 -0
- package/src/sdk/session-activation.ts +127 -0
- package/src/sdk/session.ts +144 -29
- package/src/sdk/startup-capability.ts +10 -1
- package/src/session/agent-session.ts +220 -13
- package/src/session/artifacts.ts +56 -13
- package/src/session/internal/managed-session-storage.ts +228 -7
- package/src/session/session-manager.ts +225 -60
- package/src/setup/provider-presets.json +3 -3
- package/src/task/index.ts +92 -44
- package/src/thinking-metadata.ts +2 -1
- package/src/tools/ask.ts +10 -0
- package/src/tools/index.ts +15 -0
- package/src/tools/todo-write.ts +4 -4
- package/src/utils/npm-registry.ts +660 -0
package/src/main.ts
CHANGED
|
@@ -61,11 +61,13 @@ import { SessionMigrationBusyError } from "./session/internal/session-open-error
|
|
|
61
61
|
import {
|
|
62
62
|
type ResumeSessionIdentity,
|
|
63
63
|
resolveResumableSession,
|
|
64
|
+
SESSION_OVERSIZED_RECOVERY_MESSAGE,
|
|
64
65
|
SessionArtifactCapacityError,
|
|
65
66
|
type SessionDestination,
|
|
66
67
|
type SessionDirectoryMigrationPolicy,
|
|
67
68
|
type SessionInfo,
|
|
68
69
|
SessionManager,
|
|
70
|
+
SessionTranscriptOversizedError,
|
|
69
71
|
type StrictSessionOpenResult,
|
|
70
72
|
} from "./session/session-manager";
|
|
71
73
|
import { runStartupCredentialAutoImportIfNeeded } from "./setup/credential-auto-import";
|
|
@@ -76,20 +78,13 @@ import { persistTaskTokenLog, resolveTaskTokenLogDir, taskTokenLogFromUsage } fr
|
|
|
76
78
|
import type { LspStartupServerInfo } from "./tools";
|
|
77
79
|
import { getDisplayChangelogEntries, getInstalledVersionChangelogEntry, getNewEntries } from "./utils/changelog";
|
|
78
80
|
import type { EventBus } from "./utils/event-bus";
|
|
81
|
+
import { fetchLatestPackageVersion } from "./utils/npm-registry";
|
|
79
82
|
|
|
80
83
|
async function checkForNewVersion(currentVersion: string): Promise<string | undefined> {
|
|
81
84
|
try {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const data = (await response.json()) as { version?: string };
|
|
86
|
-
const latestVersion = data.version;
|
|
87
|
-
|
|
88
|
-
if (latestVersion && Bun.semver.order(latestVersion, currentVersion) > 0) {
|
|
89
|
-
return latestVersion;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return undefined;
|
|
85
|
+
// Resolved from npm config so mirrored/firewalled networks are checked too.
|
|
86
|
+
const { version } = await fetchLatestPackageVersion("@gajae-code/coding-agent");
|
|
87
|
+
return Bun.semver.order(version, currentVersion) > 0 ? version : undefined;
|
|
93
88
|
} catch {
|
|
94
89
|
return undefined;
|
|
95
90
|
}
|
|
@@ -406,6 +401,7 @@ type StartupModelProfileArgs = {
|
|
|
406
401
|
parsedArgs: Pick<Args, "default" | "model" | "mpreset" | "thinking">;
|
|
407
402
|
startupModel?: CreateAgentSessionOptions["model"];
|
|
408
403
|
startupThinkingLevel?: CreateAgentSessionOptions["thinkingLevel"];
|
|
404
|
+
preferCachedModels?: boolean;
|
|
409
405
|
};
|
|
410
406
|
|
|
411
407
|
async function applyStartupModelProfilesWithPolicy(
|
|
@@ -416,16 +412,17 @@ async function applyStartupModelProfilesWithPolicy(
|
|
|
416
412
|
profileName: string,
|
|
417
413
|
persistDefault: boolean,
|
|
418
414
|
options: { thinkingLevelOverride?: CreateAgentSessionOptions["thinkingLevel"] } = {},
|
|
419
|
-
): Promise<
|
|
415
|
+
): Promise<boolean> => {
|
|
420
416
|
try {
|
|
421
417
|
await activateModelProfile(
|
|
422
418
|
{ session: args.session, modelRegistry: args.modelRegistry, settings: args.settings, profileName },
|
|
423
419
|
{ persistDefault, thinkingLevelOverride: options.thinkingLevelOverride },
|
|
424
420
|
);
|
|
421
|
+
return true;
|
|
425
422
|
} catch (error) {
|
|
426
423
|
if (onCredentialError && error instanceof ModelProfileCredentialError) {
|
|
427
424
|
onCredentialError(error);
|
|
428
|
-
return;
|
|
425
|
+
return false;
|
|
429
426
|
}
|
|
430
427
|
throw error;
|
|
431
428
|
}
|
|
@@ -436,19 +433,40 @@ async function applyStartupModelProfilesWithPolicy(
|
|
|
436
433
|
// deferred `--model <pattern>` path resolved inside createAgentSession.
|
|
437
434
|
const explicitModel = args.parsedArgs.model ? (args.startupModel ?? args.session.model) : undefined;
|
|
438
435
|
const defaultProfile = args.settings.get("modelProfile.default");
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
436
|
+
const preferCachedProfiles = args.preferCachedModels === true && args.parsedArgs.mpreset !== undefined;
|
|
437
|
+
const applyConfiguredProfiles = async (): Promise<boolean> => {
|
|
438
|
+
let applied = true;
|
|
439
|
+
if (defaultProfile) {
|
|
440
|
+
applied =
|
|
441
|
+
(await applyProfile(defaultProfile, false, {
|
|
442
|
+
thinkingLevelOverride: args.settings.has("defaultThinkingLevel")
|
|
443
|
+
? args.settings.get("defaultThinkingLevel")
|
|
444
|
+
: undefined,
|
|
445
|
+
})) && applied;
|
|
446
|
+
}
|
|
447
|
+
if (args.parsedArgs.mpreset) {
|
|
448
|
+
applied = (await applyProfile(args.parsedArgs.mpreset, args.parsedArgs.default === true)) && applied;
|
|
449
|
+
}
|
|
450
|
+
return applied;
|
|
451
|
+
};
|
|
442
452
|
|
|
443
|
-
if (
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
})
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
453
|
+
if (preferCachedProfiles) {
|
|
454
|
+
let applied: boolean;
|
|
455
|
+
let refreshedOnline = false;
|
|
456
|
+
try {
|
|
457
|
+
applied = await applyConfiguredProfiles();
|
|
458
|
+
} catch (error) {
|
|
459
|
+
if (error instanceof ModelProfileCredentialError) throw error;
|
|
460
|
+
await args.modelRegistry.refresh("online-if-uncached");
|
|
461
|
+
refreshedOnline = true;
|
|
462
|
+
applied = await applyConfiguredProfiles();
|
|
463
|
+
}
|
|
464
|
+
if (applied && !refreshedOnline) args.modelRegistry.refreshInBackground();
|
|
465
|
+
} else {
|
|
466
|
+
if (defaultProfile || args.parsedArgs.mpreset) {
|
|
467
|
+
await args.modelRegistry.refresh("online-if-uncached");
|
|
468
|
+
}
|
|
469
|
+
await applyConfiguredProfiles();
|
|
452
470
|
}
|
|
453
471
|
|
|
454
472
|
// Explicit CLI --model/--thinking must win over any activated or skipped profile.
|
|
@@ -509,20 +527,52 @@ export async function applyStartupModelProfilesForRoot(
|
|
|
509
527
|
resumeAction: "continue-tail" | "open-idle" | undefined;
|
|
510
528
|
},
|
|
511
529
|
): Promise<{ recoverableErrors: string[] }> {
|
|
530
|
+
const policyArgs = { ...args, preferCachedModels: args.isInteractive };
|
|
512
531
|
if (!isStartupModelProfileCredentialRecoveryEligible(args)) {
|
|
513
|
-
|
|
532
|
+
try {
|
|
533
|
+
await applyStartupModelProfilesWithPolicy(policyArgs);
|
|
534
|
+
} catch (error) {
|
|
535
|
+
await exitForStartupModelProfileError(args, error);
|
|
536
|
+
}
|
|
514
537
|
return { recoverableErrors: [] };
|
|
515
538
|
}
|
|
516
539
|
|
|
517
540
|
const recoverableErrors: string[] = [];
|
|
518
541
|
try {
|
|
519
|
-
await applyStartupModelProfilesWithPolicy(
|
|
542
|
+
await applyStartupModelProfilesWithPolicy(policyArgs, error => recoverableErrors.push(error.message));
|
|
520
543
|
} catch (error) {
|
|
521
544
|
await exitForStartupModelProfileError(args, error);
|
|
522
545
|
}
|
|
523
546
|
return { recoverableErrors };
|
|
524
547
|
}
|
|
525
548
|
|
|
549
|
+
type DeferredModelProfileStartup = () => Promise<{ recoverableErrors: string[] }>;
|
|
550
|
+
class DeferredModelProfileStartupError extends Error {
|
|
551
|
+
readonly profileError: unknown;
|
|
552
|
+
|
|
553
|
+
constructor(profileError: unknown) {
|
|
554
|
+
super("Deferred model profile startup failed");
|
|
555
|
+
this.profileError = profileError;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
async function applyDeferredStartupModelProfilesForRoot(
|
|
560
|
+
args: StartupModelProfileArgs & {
|
|
561
|
+
isInteractive: boolean;
|
|
562
|
+
hasInteractiveTerminal: boolean;
|
|
563
|
+
initialMessage: string | undefined;
|
|
564
|
+
initialMessages: readonly string[];
|
|
565
|
+
resumeAction: "continue-tail" | "open-idle" | undefined;
|
|
566
|
+
},
|
|
567
|
+
): Promise<{ recoverableErrors: string[] }> {
|
|
568
|
+
const recoverableErrors: string[] = [];
|
|
569
|
+
const onCredentialError = isStartupModelProfileCredentialRecoveryEligible(args)
|
|
570
|
+
? (error: ModelProfileCredentialError) => recoverableErrors.push(error.message)
|
|
571
|
+
: undefined;
|
|
572
|
+
await applyStartupModelProfilesWithPolicy({ ...args, preferCachedModels: true }, onCredentialError);
|
|
573
|
+
return { recoverableErrors };
|
|
574
|
+
}
|
|
575
|
+
|
|
526
576
|
interface InteractiveModeFactoryOptions {
|
|
527
577
|
session: AgentSession;
|
|
528
578
|
version: string;
|
|
@@ -572,10 +622,13 @@ function operatorFacingSessionOpenMessage(value: unknown): string | undefined {
|
|
|
572
622
|
? value.code
|
|
573
623
|
: value instanceof SessionMigrationBusyError
|
|
574
624
|
? value.code
|
|
575
|
-
:
|
|
576
|
-
? value
|
|
577
|
-
:
|
|
625
|
+
: value instanceof SessionTranscriptOversizedError
|
|
626
|
+
? value.code
|
|
627
|
+
: typeof value === "string"
|
|
628
|
+
? value
|
|
629
|
+
: undefined;
|
|
578
630
|
if (code === "artifact_capacity_exceeded") return SESSION_ARTIFACT_CAPACITY_RECOVERY_MESSAGE;
|
|
631
|
+
if (code === "oversized") return SESSION_OVERSIZED_RECOVERY_MESSAGE;
|
|
579
632
|
if (code === "migration_busy") return new SessionMigrationBusyError().message;
|
|
580
633
|
return undefined;
|
|
581
634
|
}
|
|
@@ -616,6 +669,8 @@ export async function runInteractiveMode(
|
|
|
616
669
|
initialImages?: ImageContent[],
|
|
617
670
|
createInteractiveMode?: CreateInteractiveMode,
|
|
618
671
|
resumeAction?: "continue-tail" | "open-idle",
|
|
672
|
+
startDeferredMcpConfig?: CreateAgentSessionResult["startDeferredMcpConfig"],
|
|
673
|
+
startDeferredModelProfiles?: DeferredModelProfileStartup,
|
|
619
674
|
): Promise<void> {
|
|
620
675
|
const mode = createInteractiveMode
|
|
621
676
|
? createInteractiveMode({
|
|
@@ -659,45 +714,98 @@ export async function runInteractiveMode(
|
|
|
659
714
|
mode.showStatus(notify.message);
|
|
660
715
|
}
|
|
661
716
|
}
|
|
717
|
+
if (startDeferredMcpConfig) {
|
|
718
|
+
mode.showStatus("Connecting MCP tools…");
|
|
719
|
+
void startDeferredMcpConfig()
|
|
720
|
+
.then(result => {
|
|
721
|
+
if (result.hasErrors) {
|
|
722
|
+
mode.showWarning(
|
|
723
|
+
result.loadedToolCount > 0 ? "Some MCP tools could not be loaded." : "MCP tools could not be loaded.",
|
|
724
|
+
);
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
mode.showStatus(`${result.loadedToolCount} MCP tool${result.loadedToolCount === 1 ? "" : "s"} connected`);
|
|
728
|
+
})
|
|
729
|
+
.catch(() => {
|
|
730
|
+
logger.warn("Deferred MCP startup failed.");
|
|
731
|
+
mode.showError("MCP tools could not be loaded.");
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
let deferredModelProfileFailure: Promise<never> | undefined;
|
|
735
|
+
if (startDeferredModelProfiles) {
|
|
736
|
+
mode.showStatus("Loading model profile…");
|
|
737
|
+
deferredModelProfileFailure = startDeferredModelProfiles().then(
|
|
738
|
+
result => {
|
|
739
|
+
for (const error of result.recoverableErrors) mode.showError(error);
|
|
740
|
+
mode.showStatus("Model profile ready");
|
|
741
|
+
return Promise.withResolvers<never>().promise;
|
|
742
|
+
},
|
|
743
|
+
error => {
|
|
744
|
+
const message = error instanceof Error ? error.message : "Model profile could not be loaded.";
|
|
745
|
+
mode.showError(message);
|
|
746
|
+
throw new DeferredModelProfileStartupError(error);
|
|
747
|
+
},
|
|
748
|
+
);
|
|
749
|
+
}
|
|
662
750
|
|
|
663
|
-
const
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
751
|
+
const runStartupInputAndPromptLoop = async (): Promise<never> => {
|
|
752
|
+
const hasStartupInput = initialMessage !== undefined || initialMessages.length > 0;
|
|
753
|
+
if (!hasStartupInput && resumeAction === "continue-tail") {
|
|
754
|
+
try {
|
|
755
|
+
await session.continuePersistedHistory();
|
|
756
|
+
} catch (error: unknown) {
|
|
757
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
758
|
+
mode.showError(errorMessage);
|
|
759
|
+
}
|
|
670
760
|
}
|
|
671
|
-
}
|
|
672
761
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
762
|
+
if (initialMessage !== undefined) {
|
|
763
|
+
try {
|
|
764
|
+
await session.prompt(initialMessage, { images: initialImages });
|
|
765
|
+
} catch (error: unknown) {
|
|
766
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
767
|
+
mode.showError(errorMessage);
|
|
768
|
+
}
|
|
679
769
|
}
|
|
680
|
-
}
|
|
681
770
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
771
|
+
for (const message of initialMessages) {
|
|
772
|
+
try {
|
|
773
|
+
let text = message;
|
|
774
|
+
const slashResult = await executeBuiltinSlashCommand(text, {
|
|
775
|
+
ctx: mode,
|
|
776
|
+
handleBackgroundCommand: () => mode.handleBackgroundCommand(),
|
|
777
|
+
});
|
|
778
|
+
if (slashResult === true) continue;
|
|
779
|
+
if (typeof slashResult === "string") text = slashResult;
|
|
780
|
+
await session.prompt(text);
|
|
781
|
+
} catch (error: unknown) {
|
|
782
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
783
|
+
mode.showError(errorMessage);
|
|
784
|
+
}
|
|
695
785
|
}
|
|
696
|
-
}
|
|
697
786
|
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
787
|
+
while (true) {
|
|
788
|
+
const input = await mode.getUserInput();
|
|
789
|
+
await submitInteractiveInput(mode, session, input);
|
|
790
|
+
}
|
|
791
|
+
};
|
|
792
|
+
|
|
793
|
+
const inputLoop = runStartupInputAndPromptLoop();
|
|
794
|
+
if (!deferredModelProfileFailure) {
|
|
795
|
+
await inputLoop;
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
try {
|
|
799
|
+
await Promise.race([inputLoop, deferredModelProfileFailure]);
|
|
800
|
+
} catch (error) {
|
|
801
|
+
if (!(error instanceof DeferredModelProfileStartupError)) throw error;
|
|
802
|
+
try {
|
|
803
|
+
await mode.shutdown();
|
|
804
|
+
} finally {
|
|
805
|
+
mode.stop();
|
|
806
|
+
}
|
|
807
|
+
await inputLoop.catch(() => {});
|
|
808
|
+
throw error.profileError;
|
|
701
809
|
}
|
|
702
810
|
}
|
|
703
811
|
|
|
@@ -1494,7 +1602,13 @@ export async function runRootCommand(
|
|
|
1494
1602
|
sessionOptions.notificationHostModeSupported = isInteractive;
|
|
1495
1603
|
sessionOptions.sdkHostModeSupported = isInteractive;
|
|
1496
1604
|
sessionOptions.settings = settingsInstance;
|
|
1605
|
+
if (isInteractive && sessionOptions.mcpConfigPath) {
|
|
1606
|
+
sessionOptions.deferMcpConfigStartup = true;
|
|
1607
|
+
}
|
|
1497
1608
|
const hasRootStartupProfile = Boolean(settingsInstance.get("modelProfile.default") || parsedArgs.mpreset);
|
|
1609
|
+
const deferMemoryBackendStartup =
|
|
1610
|
+
hasRootStartupProfile && !(parsedArgs.authBootstrap === true && isInteractive) && mode !== "acp";
|
|
1611
|
+
sessionOptions.deferMemoryBackendStartup = deferMemoryBackendStartup;
|
|
1498
1612
|
|
|
1499
1613
|
// Research-mode (RLM) preset: augment session options before session creation.
|
|
1500
1614
|
deps.rlmPreset?.applyOptions(sessionOptions, settingsInstance);
|
|
@@ -1574,10 +1688,16 @@ export async function runRootCommand(
|
|
|
1574
1688
|
if (!deps.suppressProcessExit) await postmortem.cleanup();
|
|
1575
1689
|
}
|
|
1576
1690
|
} else {
|
|
1577
|
-
const {
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1691
|
+
const {
|
|
1692
|
+
session,
|
|
1693
|
+
setToolUIContext,
|
|
1694
|
+
modelFallbackMessage,
|
|
1695
|
+
lspServers,
|
|
1696
|
+
mcpManager,
|
|
1697
|
+
startDeferredMcpConfig,
|
|
1698
|
+
startDeferredMemoryBackend,
|
|
1699
|
+
eventBus,
|
|
1700
|
+
} = await createSession(sessionOptions, { skipPostCreateModelRefresh: hasRootStartupProfile });
|
|
1581
1701
|
applyCliRuntimeApiKeyOverride(authStorage, parsedArgs.apiKey, session.model);
|
|
1582
1702
|
|
|
1583
1703
|
// Research-mode (RLM) preset: hard tool-boundary assertion after the registry is assembled.
|
|
@@ -1594,8 +1714,9 @@ export async function runRootCommand(
|
|
|
1594
1714
|
}
|
|
1595
1715
|
}
|
|
1596
1716
|
|
|
1717
|
+
let startDeferredModelProfiles: DeferredModelProfileStartup | undefined;
|
|
1597
1718
|
if (!(parsedArgs.authBootstrap === true && isInteractive)) {
|
|
1598
|
-
const
|
|
1719
|
+
const profileArgs = {
|
|
1599
1720
|
session,
|
|
1600
1721
|
settings: settingsInstance,
|
|
1601
1722
|
modelRegistry,
|
|
@@ -1607,9 +1728,27 @@ export async function runRootCommand(
|
|
|
1607
1728
|
initialMessage,
|
|
1608
1729
|
initialMessages: parsedArgs.messages,
|
|
1609
1730
|
resumeAction: bareResumeAction,
|
|
1610
|
-
}
|
|
1611
|
-
|
|
1612
|
-
|
|
1731
|
+
};
|
|
1732
|
+
if (isInteractive && parsedArgs.mpreset) {
|
|
1733
|
+
const ready = Promise.withResolvers<void>();
|
|
1734
|
+
session.extendStartupTurnBarrier(ready.promise);
|
|
1735
|
+
startDeferredModelProfiles = async () => {
|
|
1736
|
+
try {
|
|
1737
|
+
const result = await applyDeferredStartupModelProfilesForRoot(profileArgs);
|
|
1738
|
+
startDeferredMemoryBackend?.();
|
|
1739
|
+
ready.resolve();
|
|
1740
|
+
return result;
|
|
1741
|
+
} catch (error) {
|
|
1742
|
+
ready.reject(error);
|
|
1743
|
+
throw error;
|
|
1744
|
+
}
|
|
1745
|
+
};
|
|
1746
|
+
} else {
|
|
1747
|
+
const { recoverableErrors } = await applyStartupModelProfilesForRoot(profileArgs);
|
|
1748
|
+
startDeferredMemoryBackend?.();
|
|
1749
|
+
for (const recoverableError of recoverableErrors) {
|
|
1750
|
+
notifs.push({ kind: "error", message: recoverableError });
|
|
1751
|
+
}
|
|
1613
1752
|
}
|
|
1614
1753
|
}
|
|
1615
1754
|
|
|
@@ -1690,6 +1829,8 @@ export async function runRootCommand(
|
|
|
1690
1829
|
initialImages,
|
|
1691
1830
|
deps.createInteractiveMode,
|
|
1692
1831
|
bareResumeAction,
|
|
1832
|
+
startDeferredMcpConfig,
|
|
1833
|
+
startDeferredModelProfiles,
|
|
1693
1834
|
);
|
|
1694
1835
|
}
|
|
1695
1836
|
} catch (error) {
|
|
@@ -1698,6 +1839,9 @@ export async function runRootCommand(
|
|
|
1698
1839
|
} catch {
|
|
1699
1840
|
logger.warn("Failed to dispose session after interactive error");
|
|
1700
1841
|
}
|
|
1842
|
+
if (error !== null && typeof error === "object" && "code" in error && error.code === "cancelled") {
|
|
1843
|
+
return;
|
|
1844
|
+
}
|
|
1701
1845
|
throw error;
|
|
1702
1846
|
}
|
|
1703
1847
|
|
package/src/modes/DESIGN.md
CHANGED
|
@@ -63,9 +63,11 @@ visible-width-aligned column capped at 30 cells, two spaces, then a truncated
|
|
|
63
63
|
value. The selected row uses the themed cursor; unselected rows reserve two
|
|
64
64
|
spaces. It centers the selected item inside its `maxVisible` window, reports
|
|
65
65
|
scroll position as `(current/total)`, places a blank row before the selected
|
|
66
|
-
item description, and ends with
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
item description, and ends with a dim keyboard hint. Printable text starts a
|
|
67
|
+
case-insensitive label search shown above the list; Backspace removes one
|
|
68
|
+
grapheme, and Escape clears a non-empty search before a later Escape cancels
|
|
69
|
+
the list. Hosts that need stable height reserve fixed description rows; the
|
|
70
|
+
status-line custom editor reserves two.
|
|
69
71
|
|
|
70
72
|
Submenus are a content replacement, not a modal overlay: a bold accent title,
|
|
71
73
|
optional muted description, optional preview, a spacer, a select/list control,
|
|
@@ -82,8 +84,9 @@ empty space and list density for an operational setup flow.
|
|
|
82
84
|
### Focus, cursor, keyboard, and input behavior
|
|
83
85
|
|
|
84
86
|
- Up/Down wrap within selector lists. Enter and Space activate the current
|
|
85
|
-
action.
|
|
86
|
-
|
|
87
|
+
action. Printable text filters settings by label. Escape first clears an
|
|
88
|
+
active filter, then follows the current component's cancel path before the
|
|
89
|
+
parent is allowed to close.
|
|
87
90
|
- The parent routes Tab/Left/Right to the tab bar except while a text input is
|
|
88
91
|
active. Text entry owns arrow keys and Tab in that state.
|
|
89
92
|
- `Input` has a visible `> ` prompt, a zero-width hardware cursor marker only
|
|
@@ -403,8 +403,8 @@ function receivedSdkEvent(frame: JsonObject): ReceivedSdkEvent | undefined {
|
|
|
403
403
|
}
|
|
404
404
|
|
|
405
405
|
const ACP_CONFIG_OPTIONS = [
|
|
406
|
-
{ id: MODEL_CONFIG_ID, name: "Model", options: [] },
|
|
407
|
-
{ id: THINKING_CONFIG_ID, name: "Thinking", options: [] },
|
|
406
|
+
{ id: MODEL_CONFIG_ID, name: "Model", category: "model", options: [] },
|
|
407
|
+
{ id: THINKING_CONFIG_ID, name: "Thinking", category: "thought_level", options: [] },
|
|
408
408
|
{
|
|
409
409
|
id: "steeringMode",
|
|
410
410
|
name: "Steering queue",
|
|
@@ -466,17 +466,122 @@ function modelPresetConfigOptions(query: unknown, current: string): { value: str
|
|
|
466
466
|
return [...options].map(([value, name]) => ({ value, name }));
|
|
467
467
|
}
|
|
468
468
|
|
|
469
|
-
function modelConfigOptions(
|
|
469
|
+
function modelConfigOptions(
|
|
470
|
+
query: unknown,
|
|
471
|
+
current: string | undefined,
|
|
472
|
+
activeProviders?: ReadonlySet<string>,
|
|
473
|
+
): { value: string; name: string }[] {
|
|
470
474
|
const options = new Map<string, string>();
|
|
471
475
|
for (const item of pageItems(query)) {
|
|
472
476
|
const model = object(item);
|
|
473
477
|
if (!model || typeof model.provider !== "string" || typeof model.id !== "string") continue;
|
|
478
|
+
if (activeProviders !== undefined && !activeProviders.has(model.provider)) continue;
|
|
474
479
|
const value = `${model.provider}/${model.id}`;
|
|
475
480
|
options.set(value, typeof model.name === "string" ? model.name : value);
|
|
476
481
|
}
|
|
477
482
|
if (current && !options.has(current)) options.set(current, current);
|
|
478
483
|
return [...options].map(([value, name]) => ({ value, name }));
|
|
479
484
|
}
|
|
485
|
+
const MAX_ACTIVE_PROVIDER_PAGES = 100;
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Unsupported-query compatibility fallback. Session hosts without
|
|
489
|
+
* `providers.list/active` reject the unknown named query as either
|
|
490
|
+
* `operation_not_session_owned` (host knows the registry but not the query)
|
|
491
|
+
* or `invalid_request` (pre-Q29 host that predates the registry entry). Both
|
|
492
|
+
* keep the full catalog authoritative on the first page; every other failure
|
|
493
|
+
* mode fails closed so the active-provider contract is never silently
|
|
494
|
+
* widened.
|
|
495
|
+
*/
|
|
496
|
+
function isUnsupportedQueryError(error: unknown): boolean {
|
|
497
|
+
return (
|
|
498
|
+
typeof error === "object" &&
|
|
499
|
+
error !== null &&
|
|
500
|
+
((error as { code?: unknown }).code === "operation_not_session_owned" ||
|
|
501
|
+
(error as { code?: unknown }).code === "invalid_request")
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function queryPage(query: unknown): { items?: unknown; complete?: unknown; continuationCursor?: unknown } | undefined {
|
|
506
|
+
const response = object(query);
|
|
507
|
+
const result = object(response?.result) ?? response;
|
|
508
|
+
return object(result?.page);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Collect every page of `providers.list/active` (Q29, GJC >= 0.12.8) and
|
|
513
|
+
* return the providers with usable stored credentials or credentialless
|
|
514
|
+
* connection kinds, mirroring the TUI model picker's
|
|
515
|
+
* `modelRegistry.getAvailable()`. The openwebui-gjc-adapter applies the same
|
|
516
|
+
* filter to `/v1/models`. Q29 pages are byte-bounded and can span multiple
|
|
517
|
+
* pages when custom provider ids inflate the payload, so all pages are
|
|
518
|
+
* consumed before the provider set is built. Returns undefined only when the
|
|
519
|
+
* session host rejects the query with `operation_not_session_owned`; any
|
|
520
|
+
* other query failure or malformed page is thrown.
|
|
521
|
+
*/
|
|
522
|
+
export async function collectActiveProviderIds(
|
|
523
|
+
adapter: Pick<AcpSdkAdapter, "query">,
|
|
524
|
+
): Promise<ReadonlySet<string> | undefined> {
|
|
525
|
+
const providers = new Set<string>();
|
|
526
|
+
let cursor: string | undefined;
|
|
527
|
+
for (let pageCount = 0; pageCount < MAX_ACTIVE_PROVIDER_PAGES; pageCount++) {
|
|
528
|
+
let response: unknown;
|
|
529
|
+
try {
|
|
530
|
+
response = await adapter.query("providers.list/active", {}, cursor);
|
|
531
|
+
} catch (error) {
|
|
532
|
+
if (cursor === undefined && isUnsupportedQueryError(error)) return undefined;
|
|
533
|
+
throw error;
|
|
534
|
+
}
|
|
535
|
+
const page = queryPage(response);
|
|
536
|
+
if (!page) throw new AcpSdkAdapterError("protocol_error", "providers.list/active returned no page.");
|
|
537
|
+
const items = page.items;
|
|
538
|
+
if (!Array.isArray(items))
|
|
539
|
+
throw new AcpSdkAdapterError("protocol_error", "providers.list/active returned a malformed page.");
|
|
540
|
+
for (const item of items) {
|
|
541
|
+
const record = object(item);
|
|
542
|
+
if (!record || typeof record.provider !== "string") continue;
|
|
543
|
+
const connectionKind = record.connectionKind;
|
|
544
|
+
if (connectionKind === "credential" || connectionKind === "credentialless") providers.add(record.provider);
|
|
545
|
+
}
|
|
546
|
+
if (page.complete === true) return providers;
|
|
547
|
+
if (typeof page.continuationCursor !== "string")
|
|
548
|
+
throw new AcpSdkAdapterError(
|
|
549
|
+
"protocol_error",
|
|
550
|
+
"providers.list/active page is incomplete without a continuation cursor.",
|
|
551
|
+
);
|
|
552
|
+
cursor = page.continuationCursor;
|
|
553
|
+
}
|
|
554
|
+
throw new AcpSdkAdapterError("protocol_error", "providers.list/active exceeded the page budget.");
|
|
555
|
+
}
|
|
556
|
+
const MAX_MODEL_CATALOG_PAGES = 100;
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Collect every page of `models.list/current` (Q10) into one catalog so the
|
|
560
|
+
* active-provider filter never drops models that only appear on later pages.
|
|
561
|
+
* The SDK pages Q10 at a fixed byte target (256 KiB), which a fully
|
|
562
|
+
* configured catalog can exceed. Returns the same
|
|
563
|
+
* `{ result: { page: { items } } }` envelope `pageItems` consumes.
|
|
564
|
+
*/
|
|
565
|
+
export async function collectModelCatalog(adapter: Pick<AcpSdkAdapter, "query">): Promise<unknown> {
|
|
566
|
+
const items: unknown[] = [];
|
|
567
|
+
let cursor: string | undefined;
|
|
568
|
+
for (let pageCount = 0; pageCount < MAX_MODEL_CATALOG_PAGES; pageCount++) {
|
|
569
|
+
const response = await adapter.query("models.list/current", {}, cursor);
|
|
570
|
+
const page = queryPage(response);
|
|
571
|
+
if (!page) throw new AcpSdkAdapterError("protocol_error", "models.list/current returned no page.");
|
|
572
|
+
if (!Array.isArray(page.items))
|
|
573
|
+
throw new AcpSdkAdapterError("protocol_error", "models.list/current returned a malformed page.");
|
|
574
|
+
items.push(...page.items);
|
|
575
|
+
if (page.complete === true) return { result: { page: { items } } };
|
|
576
|
+
if (typeof page.continuationCursor !== "string")
|
|
577
|
+
throw new AcpSdkAdapterError(
|
|
578
|
+
"protocol_error",
|
|
579
|
+
"models.list/current page is incomplete without a continuation cursor.",
|
|
580
|
+
);
|
|
581
|
+
cursor = page.continuationCursor;
|
|
582
|
+
}
|
|
583
|
+
throw new AcpSdkAdapterError("protocol_error", "models.list/current exceeded the page budget.");
|
|
584
|
+
}
|
|
480
585
|
|
|
481
586
|
const THINKING_CONFIG_OPTIONS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"].map(value => ({
|
|
482
587
|
value,
|
|
@@ -484,7 +589,12 @@ const THINKING_CONFIG_OPTIONS = ["off", "minimal", "low", "medium", "high", "xhi
|
|
|
484
589
|
}));
|
|
485
590
|
|
|
486
591
|
/** Maps live canonical SDK config and the selected model catalog into the ACP 1.2.1 session state surface. */
|
|
487
|
-
export function acpSessionStateFromConfig(
|
|
592
|
+
export function acpSessionStateFromConfig(
|
|
593
|
+
query: unknown,
|
|
594
|
+
modelCatalogQuery?: unknown,
|
|
595
|
+
modelPreset?: string,
|
|
596
|
+
activeProviders?: ReadonlySet<string>,
|
|
597
|
+
) {
|
|
488
598
|
const values = configValues(query);
|
|
489
599
|
const useModelPresets = modelPreset !== undefined;
|
|
490
600
|
const currentModeId = values.get(MODE_CONFIG_ID) === ACP_PLAN_MODE_ID ? ACP_PLAN_MODE_ID : ACP_DEFAULT_MODE_ID;
|
|
@@ -493,6 +603,7 @@ export function acpSessionStateFromConfig(query: unknown, modelCatalogQuery?: un
|
|
|
493
603
|
{
|
|
494
604
|
id: MODE_CONFIG_ID,
|
|
495
605
|
name: "Mode",
|
|
606
|
+
category: "mode" as const,
|
|
496
607
|
type: "select" as const,
|
|
497
608
|
currentValue: currentModeId,
|
|
498
609
|
options: [
|
|
@@ -510,7 +621,7 @@ export function acpSessionStateFromConfig(query: unknown, modelCatalogQuery?: un
|
|
|
510
621
|
option.id === MODEL_CONFIG_ID
|
|
511
622
|
? useModelPresets
|
|
512
623
|
? modelPresetConfigOptions(modelCatalogQuery, value)
|
|
513
|
-
: modelConfigOptions(modelCatalogQuery, value)
|
|
624
|
+
: modelConfigOptions(modelCatalogQuery, value, activeProviders)
|
|
514
625
|
: option.id === THINKING_CONFIG_ID
|
|
515
626
|
? THINKING_CONFIG_OPTIONS
|
|
516
627
|
: [...option.options];
|
|
@@ -1799,8 +1910,13 @@ export class AcpAgent implements Agent {
|
|
|
1799
1910
|
const modelPreset = this.#startupOptions?.modelPreset;
|
|
1800
1911
|
const [config, modelCatalog] = await Promise.all([
|
|
1801
1912
|
record.adapter.query("config.list/get"),
|
|
1802
|
-
|
|
1913
|
+
modelPreset === undefined ? collectModelCatalog(record.adapter) : record.adapter.query("models.profiles.list"),
|
|
1803
1914
|
]);
|
|
1915
|
+
// Resolve usable providers in parallel. Only an older session host that
|
|
1916
|
+
// rejects `providers.list/active` with `operation_not_session_owned`
|
|
1917
|
+
// falls back to the full catalog; operational failures fail closed so
|
|
1918
|
+
// the active-provider contract is not silently widened.
|
|
1919
|
+
const activeProviders = modelPreset === undefined ? await collectActiveProviderIds(record.adapter) : undefined;
|
|
1804
1920
|
record.authFailure = undefined;
|
|
1805
1921
|
if (modelPreset !== undefined) {
|
|
1806
1922
|
const activePreset = configValues(config).get(MODEL_PRESET_CONFIG_KEY);
|
|
@@ -1815,7 +1931,7 @@ export class AcpAgent implements Agent {
|
|
|
1815
1931
|
throw new AcpSdkAdapterError("authentication_failed", record.authFailure);
|
|
1816
1932
|
}
|
|
1817
1933
|
}
|
|
1818
|
-
return acpSessionStateFromConfig(config, modelCatalog, modelPreset);
|
|
1934
|
+
return acpSessionStateFromConfig(config, modelCatalog, modelPreset, activeProviders);
|
|
1819
1935
|
}
|
|
1820
1936
|
|
|
1821
1937
|
async #publishAvailableCommands(id: string, adapter: AcpSdkAdapter): Promise<void> {
|
|
@@ -116,24 +116,16 @@ export class FooterComponent implements Component {
|
|
|
116
116
|
render(width: number): string[] {
|
|
117
117
|
const state = this.session.state;
|
|
118
118
|
|
|
119
|
-
//
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
totalInput += entry.message.usage.input;
|
|
130
|
-
totalOutput += entry.message.usage.output;
|
|
131
|
-
totalCacheRead += entry.message.usage.cacheRead;
|
|
132
|
-
totalCacheWrite += entry.message.usage.cacheWrite;
|
|
133
|
-
totalCost += entry.message.usage.cost.total;
|
|
134
|
-
totalPremiumRequests += entry.message.usage.premiumRequests ?? 0;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
119
|
+
// Use the session manager's cumulative index so parent and task usage are
|
|
120
|
+
// accounted for consistently without rescanning persisted entries here.
|
|
121
|
+
const {
|
|
122
|
+
input: totalInput,
|
|
123
|
+
output: totalOutput,
|
|
124
|
+
cacheRead: totalCacheRead,
|
|
125
|
+
cacheWrite: totalCacheWrite,
|
|
126
|
+
cost: totalCost,
|
|
127
|
+
premiumRequests: totalPremiumRequests,
|
|
128
|
+
} = this.session.sessionManager.getUsageStatistics();
|
|
137
129
|
|
|
138
130
|
// Calculate context usage from session (handles compaction correctly).
|
|
139
131
|
// After compaction, tokens are unknown until the next LLM response.
|