@gajae-code/coding-agent 0.18.6 → 0.18.8
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 +318 -1
- package/dist/types/async/job-manager.d.ts +1 -1
- package/dist/types/capability/mcp.d.ts +2 -0
- package/dist/types/capability/skill.d.ts +2 -0
- package/dist/types/capability/types.d.ts +6 -0
- package/dist/types/cli/public-command-errors.d.ts +3 -1
- package/dist/types/cli/setup-cli.d.ts +1 -1
- package/dist/types/commands/deep-interview.d.ts +3 -0
- package/dist/types/config/autorouting-tier-map.d.ts +348 -9
- package/dist/types/config/file-lock.d.ts +1 -0
- package/dist/types/config/model-bindings-applier.d.ts +2 -0
- package/dist/types/config/model-profile-activation.d.ts +11 -3
- package/dist/types/config/model-registry.d.ts +4 -9
- package/dist/types/config/settings.d.ts +4 -0
- package/dist/types/config/skill-settings-defaults.d.ts +2 -0
- package/dist/types/cursor-delete-path.d.ts +1 -0
- package/dist/types/cursor-mcp-name.d.ts +8 -0
- package/dist/types/customization/inventory.d.ts +2 -0
- package/dist/types/debug/report-bundle-parent.d.ts +2 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.d.ts +1 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/version-manager.d.ts +31 -0
- package/dist/types/defaults/gjc-skills.generated.d.ts +5 -6
- package/dist/types/discovery/helpers.d.ts +8 -3
- package/dist/types/export/html/css-color.d.ts +2 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/extensibility/extensions/types.d.ts +7 -0
- package/dist/types/extensibility/gjc-plugins/runtime-adapters.d.ts +14 -1
- package/dist/types/extensibility/plugins/marketplace/cache.d.ts +2 -0
- package/dist/types/extensibility/plugins/plugin-link-path.d.ts +2 -0
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +3 -1
- package/dist/types/extensibility/skill-management.d.ts +3 -1
- package/dist/types/extensibility/skills.d.ts +2 -0
- package/dist/types/gjc-runtime/deep-interview-crystallize.d.ts +74 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +11 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +5 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +0 -5
- package/dist/types/gjc-runtime/state-argv.d.ts +1 -1
- package/dist/types/gjc-runtime/state-migrations.d.ts +8 -0
- package/dist/types/gjc-runtime/state-runtime.d.ts +95 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +11 -1
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +8 -0
- package/dist/types/internal-urls/skill-path.d.ts +1 -0
- package/dist/types/lsp/workspace-path.d.ts +1 -0
- package/dist/types/modes/acp/acp-agent.d.ts +7 -0
- package/dist/types/modes/components/custom-editor.d.ts +6 -0
- package/dist/types/modes/components/customization/customization-dashboard.d.ts +1 -0
- package/dist/types/modes/components/extensions/state-manager.d.ts +4 -3
- package/dist/types/modes/controllers/input-controller.d.ts +9 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/utils/interrupt-ownership.d.ts +3 -0
- package/dist/types/modes/utils/keybinding-matchers.d.ts +1 -0
- package/dist/types/modes/utils/macos-option-forwarding.d.ts +17 -0
- package/dist/types/progress/collect-project-progress.d.ts +38 -0
- package/dist/types/progress/progress-contract.d.ts +154 -0
- package/dist/types/progress/progress-snapshot.d.ts +11 -0
- package/dist/types/progress/project-progress.d.ts +141 -0
- package/dist/types/progress/render-progress.d.ts +28 -0
- package/dist/types/runtime-mcp/elicitation.d.ts +12 -0
- package/dist/types/runtime-mcp/oauth-public-url.d.ts +1 -0
- package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +1 -0
- package/dist/types/runtime-mcp/smithery-registry.d.ts +24 -0
- package/dist/types/runtime-mcp/types.d.ts +6 -0
- package/dist/types/sdk/acp/adapter.d.ts +1 -1
- package/dist/types/sdk/broker/broker-exit.d.ts +6 -2
- package/dist/types/sdk/broker/broker.d.ts +18 -1
- package/dist/types/sdk/broker/lifecycle.d.ts +9 -0
- package/dist/types/sdk/broker/session-index.d.ts +12 -6
- package/dist/types/sdk/broker/task-artifact-owner-validation.d.ts +55 -0
- package/dist/types/sdk/bus/index.d.ts +12 -1
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +6 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +3 -2
- package/dist/types/sdk/client/client.d.ts +2 -0
- package/dist/types/sdk/host/host.d.ts +6 -0
- package/dist/types/sdk/host/query/handlers.d.ts +3 -0
- package/dist/types/sdk/index.d.ts +1 -0
- package/dist/types/sdk/router/session-router.d.ts +9 -0
- package/dist/types/sdk/session-list.d.ts +1 -1
- package/dist/types/sdk/session.d.ts +8 -0
- package/dist/types/session/agent-session.d.ts +38 -9
- package/dist/types/session/artifacts.d.ts +2 -2
- package/dist/types/session/internal/managed-gc-retirement-codec.d.ts +32 -0
- package/dist/types/session/internal/managed-session-scope.d.ts +79 -2
- package/dist/types/session/internal/managed-session-storage.d.ts +36 -1
- package/dist/types/session/internal/managed-task-owner-cleanup.d.ts +62 -0
- package/dist/types/session/internal/task-artifact-owner-access.d.ts +19 -0
- package/dist/types/session/internal/task-artifact-owner-transcript.d.ts +11 -0
- package/dist/types/session/session-manager.d.ts +31 -5
- package/dist/types/session/session-retirement.d.ts +71 -0
- package/dist/types/session/session-storage.d.ts +62 -48
- package/dist/types/session/session-transcript-header.d.ts +2 -0
- package/dist/types/session/task-artifact-owner-codec.d.ts +109 -0
- package/dist/types/session/task-artifact-owner-retirement.d.ts +7 -0
- package/dist/types/session/task-artifact-owner.d.ts +10 -0
- package/dist/types/setup/credential-auto-import.d.ts +1 -1
- package/dist/types/setup/credential-import.d.ts +6 -4
- package/dist/types/skill-state/active-state.d.ts +38 -0
- package/dist/types/slash-commands/helpers/progress-report.d.ts +8 -0
- package/dist/types/task/executor.d.ts +3 -0
- package/dist/types/tools/bash-skill-urls.d.ts +1 -1
- package/dist/types/tools/index.d.ts +3 -0
- package/dist/types/tools/python.d.ts +9 -1
- package/dist/types/tools/recipe/recipe-guard.d.ts +7 -0
- package/dist/types/tools/restricted-cwd.d.ts +1 -0
- package/dist/types/tools/todo-write.d.ts +6 -0
- package/dist/types/utils/tools-checksum.d.ts +7 -0
- package/package.json +7 -7
- package/scripts/generate-sdk-operation-inventory.ts +21 -0
- package/scripts/resident-memory-bench.ts +174 -20
- package/scripts/verify-managed-empty-stop.ts +28 -0
- package/src/async/job-manager.ts +228 -122
- package/src/capability/index.ts +18 -0
- package/src/capability/mcp.ts +2 -0
- package/src/capability/skill.ts +2 -0
- package/src/capability/types.ts +6 -0
- package/src/cli/auth-broker-cli.ts +1 -1
- package/src/cli/config-cli.ts +8 -18
- package/src/cli/customize-doctor.ts +15 -2
- package/src/cli/public-command-errors.ts +21 -1
- package/src/cli/setup-cli.ts +4 -4
- package/src/cli/skills-cli.ts +5 -0
- package/src/cli-main.ts +7 -8
- package/src/commands/deep-interview.ts +47 -8
- package/src/commands/sdk.ts +140 -29
- package/src/config/autorouting-tier-map.ts +117 -7
- package/src/config/file-lock.ts +307 -68
- package/src/config/mcp-schema.json +4 -0
- package/src/config/model-bindings-applier.ts +20 -0
- package/src/config/model-profile-activation.ts +240 -46
- package/src/config/model-registry.ts +47 -153
- package/src/config/settings.ts +44 -4
- package/src/config/skill-settings-defaults.ts +7 -0
- package/src/coordinator-mcp/policy.ts +3 -2
- package/src/cursor-delete-path.ts +10 -0
- package/src/cursor-mcp-name.ts +15 -0
- package/src/cursor.ts +13 -2
- package/src/customization/inventory.ts +3 -0
- package/src/debug/report-bundle-parent.ts +22 -0
- package/src/debug/report-bundle.ts +3 -0
- package/src/debug/system-info.ts +13 -1
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.ts +67 -4
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/version-manager.ts +80 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +105 -10
- package/src/defaults/gjc/skills/ralplan/SKILL.md +6 -4
- package/src/defaults/gjc-defaults.test.ts +18 -1
- package/src/defaults/gjc-defaults.ts +3 -0
- package/src/defaults/gjc-skills.generated.ts +58 -36
- package/src/discovery/agents.ts +6 -1
- package/src/discovery/builtin.ts +10 -0
- package/src/discovery/helpers.ts +168 -23
- package/src/discovery/mcp-json.ts +6 -0
- package/src/discovery/ssh.ts +9 -4
- package/src/discovery/substitute-plugin-root.ts +3 -1
- package/src/edit/file-read-cache.ts +1 -1
- package/src/eval/py/executor.ts +380 -120
- package/src/export/html/css-color.ts +9 -0
- package/src/export/html/index.ts +5 -4
- package/src/export/html/template.generated.ts +1 -1
- package/src/extensibility/extensions/runner.ts +4 -0
- package/src/extensibility/extensions/types.ts +7 -0
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +73 -8
- package/src/extensibility/plugins/manager.ts +3 -2
- package/src/extensibility/plugins/marketplace/cache.ts +15 -0
- package/src/extensibility/plugins/marketplace/manager.ts +11 -3
- package/src/extensibility/plugins/plugin-link-path.ts +12 -0
- package/src/extensibility/runtime-skill-discovery.ts +20 -2
- package/src/extensibility/skill-management.ts +6 -2
- package/src/extensibility/skills.ts +6 -1
- package/src/gjc-runtime/deep-interview-crystallize.ts +1814 -0
- package/src/gjc-runtime/deep-interview-recorder.ts +20 -2
- package/src/gjc-runtime/deep-interview-runtime.ts +1415 -13
- package/src/gjc-runtime/deep-interview-stage.ts +98 -21
- package/src/gjc-runtime/gc-runtime.ts +206 -8
- package/src/gjc-runtime/launch-worktree.ts +14 -4
- package/src/gjc-runtime/ralplan-runtime.ts +447 -52
- package/src/gjc-runtime/state-argv.ts +2 -0
- package/src/gjc-runtime/state-migrations.ts +91 -29
- package/src/gjc-runtime/state-runtime.ts +3716 -135
- package/src/gjc-runtime/state-writer.ts +83 -8
- package/src/gjc-runtime/ultragoal-runtime.ts +16 -4
- package/src/gjc-runtime/workflow-manifest.generated.json +70 -42
- package/src/gjc-runtime/workflow-manifest.ts +54 -21
- package/src/harness-control-plane/operate.ts +12 -4
- package/src/harness-control-plane/owner.ts +5 -1
- package/src/hashline/parser.ts +48 -9
- package/src/hashline/recovery.ts +13 -4
- package/src/hindsight/config.ts +9 -3
- package/src/internal-urls/docs-index.generated.ts +19 -18
- package/src/internal-urls/skill-path.ts +19 -0
- package/src/internal-urls/skill-protocol.ts +2 -0
- package/src/lsp/edits.ts +6 -0
- package/src/lsp/workspace-path.ts +51 -0
- package/src/main.ts +195 -31
- package/src/modes/acp/acp-agent.ts +163 -7
- package/src/modes/components/custom-editor.ts +14 -0
- package/src/modes/components/customization/customization-dashboard.ts +12 -0
- package/src/modes/components/extensions/extension-dashboard.ts +2 -2
- package/src/modes/components/extensions/state-manager.ts +15 -5
- package/src/modes/components/model-selector.ts +1 -4
- package/src/modes/components/provider-onboarding-selector.ts +1 -1
- package/src/modes/components/status-line/segments.ts +4 -0
- package/src/modes/controllers/event-controller.ts +11 -4
- package/src/modes/controllers/input-controller.ts +74 -29
- package/src/modes/controllers/runtime-mcp-command-controller.ts +8 -3
- package/src/modes/controllers/selector-controller.ts +28 -15
- package/src/modes/interactive-mode.ts +4 -0
- package/src/modes/runtime-init.ts +4 -0
- package/src/modes/utils/abort-message.ts +3 -1
- package/src/modes/utils/interrupt-ownership.ts +12 -0
- package/src/modes/utils/keybinding-matchers.ts +4 -0
- package/src/modes/utils/macos-option-forwarding.ts +80 -0
- package/src/progress/collect-project-progress.ts +153 -0
- package/src/progress/progress-contract.ts +172 -0
- package/src/progress/progress-snapshot.ts +140 -0
- package/src/progress/project-progress.ts +446 -0
- package/src/progress/render-progress.ts +209 -0
- package/src/prompts/system/master-mode.md +1 -0
- package/src/prompts/tools/skill-discovery.md +1 -1
- package/src/runtime-mcp/client.ts +13 -4
- package/src/runtime-mcp/config-writer.ts +10 -6
- package/src/runtime-mcp/config.ts +1 -0
- package/src/runtime-mcp/elicitation.ts +142 -0
- package/src/runtime-mcp/oauth-flow.ts +5 -0
- package/src/runtime-mcp/oauth-public-url.ts +8 -0
- package/src/runtime-mcp/plugin-network-boundary.ts +4 -0
- package/src/runtime-mcp/smithery-registry.ts +22 -3
- package/src/runtime-mcp/transports/http.ts +2 -2
- package/src/runtime-mcp/types.ts +6 -0
- package/src/sdk/acp/adapter.ts +93 -8
- package/src/sdk/broker/broker-exit.ts +52 -23
- package/src/sdk/broker/broker.ts +83 -7
- package/src/sdk/broker/ensure.ts +2 -0
- package/src/sdk/broker/lifecycle.ts +820 -73
- package/src/sdk/broker/session-index.ts +191 -81
- package/src/sdk/broker/task-artifact-owner-validation.ts +259 -0
- package/src/sdk/bus/chat-daemon-cli.ts +38 -1
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +3 -0
- package/src/sdk/bus/index.ts +241 -46
- package/src/sdk/bus/telegram-daemon-contract.ts +6 -1
- package/src/sdk/bus/telegram-daemon.ts +4 -2
- package/src/sdk/cli/session-cli.ts +17 -3
- package/src/sdk/client/client.ts +28 -8
- package/src/sdk/host/host.ts +6 -0
- package/src/sdk/host/query/handlers.ts +9 -1
- package/src/sdk/host/session-runtime.test.ts +2805 -411
- package/src/sdk/host/session-runtime.ts +428 -111
- package/src/sdk/host/surface-policy.ts +1 -0
- package/src/sdk/index.ts +8 -0
- package/src/sdk/lifecycle-session.ts +3 -1
- package/src/sdk/mcp/server.ts +15 -3
- package/src/sdk/prompt-deadline-manager.ts +15 -0
- package/src/sdk/protocol/operation-inventory.generated.json +146 -0
- package/src/sdk/protocol/operation-registry.ts +9 -2
- package/src/sdk/router/session-router.ts +94 -14
- package/src/sdk/session-list.ts +1 -0
- package/src/sdk/session.ts +81 -38
- package/src/session/agent-session.ts +674 -224
- package/src/session/artifacts.ts +174 -70
- package/src/session/internal/managed-gc-retirement-codec.ts +319 -0
- package/src/session/internal/managed-session-scope.ts +3457 -462
- package/src/session/internal/managed-session-storage.ts +482 -61
- package/src/session/internal/managed-task-owner-cleanup.ts +473 -0
- package/src/session/internal/task-artifact-owner-access.ts +245 -0
- package/src/session/internal/task-artifact-owner-transcript.ts +614 -0
- package/src/session/session-manager.ts +939 -133
- package/src/session/session-retirement.ts +714 -0
- package/src/session/session-storage.ts +418 -174
- package/src/session/session-transcript-header.ts +17 -0
- package/src/session/task-artifact-owner-codec.ts +641 -0
- package/src/session/task-artifact-owner-retirement.ts +477 -0
- package/src/session/task-artifact-owner.ts +289 -0
- package/src/setup/credential-auto-import.ts +12 -3
- package/src/setup/credential-import.ts +176 -5
- package/src/setup/paseo/announce.ts +6 -1
- package/src/skill-state/active-state.ts +132 -2
- package/src/slash-commands/builtin-registry.ts +39 -9
- package/src/slash-commands/helpers/progress-report.ts +23 -0
- package/src/stt/setup.ts +3 -5
- package/src/task/commands.ts +3 -1
- package/src/task/executor.ts +4 -0
- package/src/task/index.ts +6 -0
- package/src/tools/ask.ts +205 -6
- package/src/tools/ast-edit.ts +4 -0
- package/src/tools/bash-allowed-prefixes.ts +15 -0
- package/src/tools/bash-skill-urls.ts +56 -3
- package/src/tools/bash.ts +48 -79
- package/src/tools/descriptors.ts +5 -1
- package/src/tools/eval.ts +56 -36
- package/src/tools/image-gen.ts +1 -1
- package/src/tools/index.ts +3 -0
- package/src/tools/python.ts +187 -66
- package/src/tools/recipe/index.ts +9 -0
- package/src/tools/recipe/recipe-guard.ts +19 -0
- package/src/tools/restricted-cwd.ts +10 -0
- package/src/tools/skill-discovery.ts +5 -0
- package/src/tools/skill.ts +28 -1
- package/src/tools/todo-write.ts +10 -1
- package/src/tools/tool-catalog.generated.ts +1 -1
- package/src/tools/write.ts +1 -0
- package/src/utils/tools-checksum.ts +34 -0
- package/src/utils/tools-manager.ts +24 -6
- package/src/web/search/providers/codex.ts +3 -2
|
@@ -40,7 +40,9 @@ import { CommandPalette, type CommandPaletteAction, type CommandPaletteEntry } f
|
|
|
40
40
|
import type { PasteTextContext } from "../components/custom-editor";
|
|
41
41
|
import { QueuePaneComponent } from "../components/queue-pane";
|
|
42
42
|
import { type QueuedMessageMoveDirection, QueuedMessageSelectorComponent } from "../components/queued-message-selector";
|
|
43
|
+
import { focusedUiOwnsInterrupt } from "../utils/interrupt-ownership";
|
|
43
44
|
import { matchesAppInterrupt } from "../utils/keybinding-matchers";
|
|
45
|
+
import { createUnforwardedOptionChordWarner } from "../utils/macos-option-forwarding";
|
|
44
46
|
|
|
45
47
|
const QUEUE_SELECTOR_NAVIGATION_ACTIONS = [
|
|
46
48
|
"tui.select.up",
|
|
@@ -95,6 +97,8 @@ function isExpandable(obj: unknown): obj is Expandable {
|
|
|
95
97
|
export class InputController {
|
|
96
98
|
readonly actionRegistry: ActionRegistry<void>;
|
|
97
99
|
readonly #loadPastedImageBatch: typeof loadPastedImageBatch;
|
|
100
|
+
/** Session id with an automatic-title request pending, if any. */
|
|
101
|
+
#autoTitleInFlightSessionId: string | undefined;
|
|
98
102
|
#deferredSubmission?: {
|
|
99
103
|
text: string;
|
|
100
104
|
images?: InteractiveModeContext["pendingImages"];
|
|
@@ -546,15 +550,23 @@ export class InputController {
|
|
|
546
550
|
if (!isInterruptKey && !isClearKey) {
|
|
547
551
|
return undefined;
|
|
548
552
|
}
|
|
549
|
-
|
|
553
|
+
// Listeners run before focused handleInput. Yield interrupt/back to the
|
|
554
|
+
// focused UI before touching BTW, maintenance, retries or workflow state.
|
|
555
|
+
// Clear (Ctrl+C by default) remains a global abort, even if the same
|
|
556
|
+
// key is also configured as interrupt.
|
|
557
|
+
if (isInterruptKey && !isClearKey && focusedUiOwnsInterrupt(this.ctx)) {
|
|
558
|
+
this.#resetEscapeGestures();
|
|
559
|
+
return undefined;
|
|
560
|
+
}
|
|
561
|
+
if (isClearKey && this.ctx.hasActiveBtw() && this.ctx.handleBtwEscape()) {
|
|
550
562
|
this.#resetEscapeGestures();
|
|
551
563
|
return { consume: true };
|
|
552
564
|
}
|
|
553
|
-
if (isClearKey &&
|
|
565
|
+
if (isClearKey && this.ctx.hookSelector?.hasActiveInlineInput?.() === true) {
|
|
554
566
|
this.#resetEscapeGestures();
|
|
555
567
|
return undefined;
|
|
556
568
|
}
|
|
557
|
-
if (isClearKey
|
|
569
|
+
if (isClearKey) {
|
|
558
570
|
if (this.#handlePendingSteerInterrupt()) return { consume: true };
|
|
559
571
|
if (
|
|
560
572
|
this.#handleCancellableWorkEscape({
|
|
@@ -807,6 +819,12 @@ export class InputController {
|
|
|
807
819
|
this.ctx.editor.setActionKeys("app.message.queue", this.ctx.keybindings.getKeys("app.message.queue"));
|
|
808
820
|
this.ctx.editor.onQueue = () => this.#executeAction("app.message.queue");
|
|
809
821
|
this.#registerCommandPaletteAction("app.message.queue", queue, true);
|
|
822
|
+
this.ctx.editor.onUnforwardedOptionChord ??= createUnforwardedOptionChordWarner({
|
|
823
|
+
platform: process.platform,
|
|
824
|
+
terminalProgram: Bun.env.TERM_PROGRAM,
|
|
825
|
+
formatKeyHint: key => this.ctx.keybindings.formatKeyHint(key),
|
|
826
|
+
showWarning: message => this.ctx.showWarning(message),
|
|
827
|
+
});
|
|
810
828
|
|
|
811
829
|
this.ctx.editor.onViewportPageScroll = direction => this.ctx.ui.scrollViewportPages(direction);
|
|
812
830
|
this.ctx.editor.onViewportFollowLive = () => {
|
|
@@ -1163,6 +1181,7 @@ export class InputController {
|
|
|
1163
1181
|
return;
|
|
1164
1182
|
}
|
|
1165
1183
|
this.ctx.queueCompactionMessage(text, "steer", composer);
|
|
1184
|
+
this.#titleQueuedCompactionText(text);
|
|
1166
1185
|
return;
|
|
1167
1186
|
}
|
|
1168
1187
|
|
|
@@ -1172,6 +1191,9 @@ export class InputController {
|
|
|
1172
1191
|
// This handles extension commands (execute immediately), prompt template expansion, and queueing
|
|
1173
1192
|
if (this.ctx.session.isStreaming) {
|
|
1174
1193
|
invalidateSessionTitleGeneration(this.ctx.sessionManager);
|
|
1194
|
+
// A turn started by a skill command has no user message yet, so the
|
|
1195
|
+
// first typed message can arrive here instead of the idle path.
|
|
1196
|
+
this.maybeGenerateSessionTitle(text);
|
|
1175
1197
|
if (this.#canModifyComposer(composer)) {
|
|
1176
1198
|
this.ctx.editor.addToHistory(text);
|
|
1177
1199
|
this.ctx.editor.setText("");
|
|
@@ -1200,32 +1222,7 @@ export class InputController {
|
|
|
1200
1222
|
// First, move any pending bash components to chat
|
|
1201
1223
|
this.ctx.flushPendingBashComponents();
|
|
1202
1224
|
|
|
1203
|
-
|
|
1204
|
-
const hasUserMessages = this.ctx.session.messages.some((m: AgentMessage) => m.role === "user");
|
|
1205
|
-
if (!hasUserMessages && !this.ctx.sessionManager.getSessionName() && !$pickenv("GJC_NO_TITLE", "PI_NO_TITLE")) {
|
|
1206
|
-
const registry = this.ctx.session.modelRegistry;
|
|
1207
|
-
generateSessionTitle(
|
|
1208
|
-
text,
|
|
1209
|
-
registry,
|
|
1210
|
-
this.ctx.settings,
|
|
1211
|
-
this.ctx.session.credentialSessionId,
|
|
1212
|
-
this.ctx.session.model,
|
|
1213
|
-
provider => this.ctx.session.agent.metadataForProvider(provider),
|
|
1214
|
-
)
|
|
1215
|
-
.then(async title => {
|
|
1216
|
-
if (title) {
|
|
1217
|
-
const applied = await this.ctx.sessionManager.setSessionName(title, "auto");
|
|
1218
|
-
if (applied) {
|
|
1219
|
-
setSessionTerminalTitle(
|
|
1220
|
-
this.ctx.sessionManager.getSessionName()!,
|
|
1221
|
-
this.ctx.sessionManager.getCwd(),
|
|
1222
|
-
);
|
|
1223
|
-
this.ctx.updateEditorBorderColor();
|
|
1224
|
-
}
|
|
1225
|
-
}
|
|
1226
|
-
})
|
|
1227
|
-
.catch(() => {});
|
|
1228
|
-
}
|
|
1225
|
+
this.maybeGenerateSessionTitle(text);
|
|
1229
1226
|
|
|
1230
1227
|
if (this.ctx.onInputCallback) {
|
|
1231
1228
|
// Include any pending images from clipboard paste
|
|
@@ -1628,6 +1625,51 @@ export class InputController {
|
|
|
1628
1625
|
return this.ctx.settings.get("busyPromptMode") === "steer" ? "steer" : "followUp";
|
|
1629
1626
|
}
|
|
1630
1627
|
|
|
1628
|
+
/**
|
|
1629
|
+
* Generate the automatic session title from the first user message. Called
|
|
1630
|
+
* from the editor submit and queue-shortcut paths (idle and streaming) and
|
|
1631
|
+
* from interactive startup messages; it is a no-op once the session has a
|
|
1632
|
+
* user message, a name, or a title request already in flight. The result is
|
|
1633
|
+
* bound to the session that requested it: a completion that lands after the
|
|
1634
|
+
* session was replaced (New Session, switch, resume) is discarded.
|
|
1635
|
+
*/
|
|
1636
|
+
maybeGenerateSessionTitle(text: string): void {
|
|
1637
|
+
if (this.ctx.sessionManager.getSessionName()) return;
|
|
1638
|
+
const sessionId = this.ctx.sessionManager.getSessionId();
|
|
1639
|
+
if (this.#autoTitleInFlightSessionId === sessionId) return;
|
|
1640
|
+
if ($pickenv("GJC_NO_TITLE", "PI_NO_TITLE")) return;
|
|
1641
|
+
if (this.ctx.session.messages.some((m: AgentMessage) => m.role === "user")) return;
|
|
1642
|
+
// Keep locally intercepted command arguments away from the title model.
|
|
1643
|
+
if (this.ctx.session.isLocallyHandledSlashCommand(text)) return;
|
|
1644
|
+
this.#autoTitleInFlightSessionId = sessionId;
|
|
1645
|
+
generateSessionTitle(
|
|
1646
|
+
text,
|
|
1647
|
+
this.ctx.session.modelRegistry,
|
|
1648
|
+
this.ctx.settings,
|
|
1649
|
+
this.ctx.session.credentialSessionId,
|
|
1650
|
+
this.ctx.session.model,
|
|
1651
|
+
provider => this.ctx.session.agent.metadataForProvider(provider),
|
|
1652
|
+
)
|
|
1653
|
+
.then(async title => {
|
|
1654
|
+
if (!title || this.ctx.sessionManager.getSessionId() !== sessionId) return;
|
|
1655
|
+
const applied = await this.ctx.sessionManager.setSessionName(title, "auto");
|
|
1656
|
+
if (applied) {
|
|
1657
|
+
setSessionTerminalTitle(this.ctx.sessionManager.getSessionName()!, this.ctx.sessionManager.getCwd());
|
|
1658
|
+
this.ctx.updateEditorBorderColor();
|
|
1659
|
+
}
|
|
1660
|
+
})
|
|
1661
|
+
.catch(() => {})
|
|
1662
|
+
.finally(() => {
|
|
1663
|
+
if (this.#autoTitleInFlightSessionId === sessionId) this.#autoTitleInFlightSessionId = undefined;
|
|
1664
|
+
});
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
/** Title text accepted into the compaction queue; skill commands replay through the skill path and are not titled. */
|
|
1668
|
+
#titleQueuedCompactionText(text: string): void {
|
|
1669
|
+
if (parseSkillInvocations(text, this.ctx.skillCommands ?? new Map()).length > 0) return;
|
|
1670
|
+
this.maybeGenerateSessionTitle(text);
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1631
1673
|
/**
|
|
1632
1674
|
* Dispatch skill slash invocation(s) (`/skill:<name>`) through custom messages
|
|
1633
1675
|
* using the supplied `streamingBehavior`. Returns true if the text contains a
|
|
@@ -1736,6 +1778,7 @@ export class InputController {
|
|
|
1736
1778
|
return;
|
|
1737
1779
|
}
|
|
1738
1780
|
this.ctx.queueCompactionMessage(text, "followUp");
|
|
1781
|
+
this.#titleQueuedCompactionText(text);
|
|
1739
1782
|
return;
|
|
1740
1783
|
}
|
|
1741
1784
|
|
|
@@ -1750,6 +1793,7 @@ export class InputController {
|
|
|
1750
1793
|
followUpQueuePolicy: "sequential",
|
|
1751
1794
|
}),
|
|
1752
1795
|
);
|
|
1796
|
+
this.maybeGenerateSessionTitle(text);
|
|
1753
1797
|
this.ctx.updatePendingMessagesDisplay();
|
|
1754
1798
|
this.ctx.ui.requestRender();
|
|
1755
1799
|
return;
|
|
@@ -1761,6 +1805,7 @@ export class InputController {
|
|
|
1761
1805
|
return;
|
|
1762
1806
|
}
|
|
1763
1807
|
|
|
1808
|
+
this.maybeGenerateSessionTitle(text);
|
|
1764
1809
|
if (this.ctx.session.isStreaming) {
|
|
1765
1810
|
this.ctx.editor.addToHistory(text);
|
|
1766
1811
|
this.ctx.editor.setText("");
|
|
@@ -44,7 +44,8 @@ import { parseCommandArgs } from "../shared";
|
|
|
44
44
|
import { buildOAuthLoginAnchor, createOAuthUrlCopyLease } from "../shared/oauth-url-copy";
|
|
45
45
|
import { theme } from "../theme/theme";
|
|
46
46
|
import type { InteractiveModeContext } from "../types";
|
|
47
|
-
import {
|
|
47
|
+
import { focusedUiOwnsInterrupt } from "../utils/interrupt-ownership";
|
|
48
|
+
import { matchesAppClear, matchesAppInterrupt } from "../utils/keybinding-matchers";
|
|
48
49
|
import { groupBySource, parseRemoveArgs, readScopeFlag, showCommandMessage } from "./command-controller-shared";
|
|
49
50
|
|
|
50
51
|
function withTimeout<T>(promise: Promise<T>, timeoutMs: number, message: string): Promise<T> {
|
|
@@ -645,7 +646,9 @@ export class MCPCommandController {
|
|
|
645
646
|
const cancellationUnsubscribe =
|
|
646
647
|
signal === undefined && typeof this.ctx.ui.addInputListener === "function"
|
|
647
648
|
? this.ctx.ui.addInputListener(data => {
|
|
648
|
-
|
|
649
|
+
const isClearKey = data === "\x03" || matchesAppClear(data);
|
|
650
|
+
if (!isClearKey && !matchesAppInterrupt(data)) return;
|
|
651
|
+
if (!isClearKey && focusedUiOwnsInterrupt(this.ctx)) return;
|
|
649
652
|
userController.abort(new Error("OAuth flow cancelled"));
|
|
650
653
|
return { consume: true };
|
|
651
654
|
})
|
|
@@ -1839,7 +1842,9 @@ export class MCPCommandController {
|
|
|
1839
1842
|
const oauthUrlCopyLease = createOAuthUrlCopyLease(this.ctx);
|
|
1840
1843
|
const abortController = new AbortController();
|
|
1841
1844
|
const interruptUnsubscribe = this.ctx.ui.addInputListener?.(data => {
|
|
1842
|
-
|
|
1845
|
+
const isClearKey = data === "\x03" || matchesAppClear(data);
|
|
1846
|
+
if (!isClearKey && !matchesAppInterrupt(data)) return;
|
|
1847
|
+
if (!isClearKey && focusedUiOwnsInterrupt(this.ctx)) return;
|
|
1843
1848
|
abortController.abort(new Error("Smithery authorization cancelled."));
|
|
1844
1849
|
return { consume: true };
|
|
1845
1850
|
});
|
|
@@ -1612,7 +1612,7 @@ export class SelectorController {
|
|
|
1612
1612
|
}
|
|
1613
1613
|
|
|
1614
1614
|
async #handleCredentialImport(): Promise<void> {
|
|
1615
|
-
this.ctx.showStatus("Scanning for existing Claude Code / Codex CLI credentials…");
|
|
1615
|
+
this.ctx.showStatus("Scanning for existing Claude Code / Codex CLI / Kiro CLI credentials…");
|
|
1616
1616
|
const preview = await runExternalCredentialAutoImport({
|
|
1617
1617
|
authStorage: {
|
|
1618
1618
|
importCredentialIfAbsent: async () => ({
|
|
@@ -2494,17 +2494,20 @@ export class SelectorController {
|
|
|
2494
2494
|
* default) then refresh the status surfaces. Rethrows so callers surface errors.
|
|
2495
2495
|
*/
|
|
2496
2496
|
async #applyModelProfile(profileName: string, persistDefault: boolean): Promise<void> {
|
|
2497
|
+
this.ctx.session.markUserModelSelection();
|
|
2497
2498
|
const profileLabel = formatModelProfileDisplayLabel(
|
|
2498
2499
|
this.ctx.session.modelRegistry.getModelProfile(profileName) ?? { name: profileName },
|
|
2499
2500
|
);
|
|
2500
|
-
await
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2501
|
+
await this.ctx.session.withSdkControlMutation(() =>
|
|
2502
|
+
activateModelProfile(
|
|
2503
|
+
{
|
|
2504
|
+
session: this.ctx.session,
|
|
2505
|
+
modelRegistry: this.ctx.session.modelRegistry,
|
|
2506
|
+
settings: this.ctx.settings,
|
|
2507
|
+
profileName,
|
|
2508
|
+
},
|
|
2509
|
+
{ persistDefault },
|
|
2510
|
+
),
|
|
2508
2511
|
);
|
|
2509
2512
|
this.ctx.statusLine.invalidate();
|
|
2510
2513
|
this.ctx.updateEditorBorderColor();
|
|
@@ -2774,8 +2777,15 @@ export class SelectorController {
|
|
|
2774
2777
|
return;
|
|
2775
2778
|
}
|
|
2776
2779
|
const { model, role, thinkingLevel, selector: selectedSelector } = selection;
|
|
2780
|
+
if (
|
|
2781
|
+
(role !== null && role !== "default") ||
|
|
2782
|
+
selection.roles?.some(targetRole => targetRole !== "default")
|
|
2783
|
+
) {
|
|
2784
|
+
this.ctx.session.markUserModelSelection();
|
|
2785
|
+
}
|
|
2777
2786
|
if (role === null) {
|
|
2778
2787
|
// Temporary: update agent state but don't persist to settings
|
|
2788
|
+
this.ctx.session.markUserModelSelection();
|
|
2779
2789
|
await this.ctx.session.setModelTemporary(model, thinkingLevel, {
|
|
2780
2790
|
cause: "temporary-operation",
|
|
2781
2791
|
reason: "other",
|
|
@@ -3677,12 +3687,15 @@ export class SelectorController {
|
|
|
3677
3687
|
return;
|
|
3678
3688
|
}
|
|
3679
3689
|
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3690
|
+
this.ctx.session.markUserModelSelection();
|
|
3691
|
+
await this.ctx.session.withSdkControlMutation(() =>
|
|
3692
|
+
activateModelProfile({
|
|
3693
|
+
session: this.ctx.session,
|
|
3694
|
+
modelRegistry: this.ctx.session.modelRegistry,
|
|
3695
|
+
settings: this.ctx.settings,
|
|
3696
|
+
profileName: recommendedProfile.name,
|
|
3697
|
+
}),
|
|
3698
|
+
);
|
|
3686
3699
|
}
|
|
3687
3700
|
|
|
3688
3701
|
async #handleOAuthLogin(providerId: string, options?: OAuthSelectorOptions): Promise<void> {
|
|
@@ -2642,6 +2642,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2642
2642
|
this.#inputController.handleBackgroundCommand();
|
|
2643
2643
|
}
|
|
2644
2644
|
|
|
2645
|
+
maybeGenerateSessionTitle(text: string): void {
|
|
2646
|
+
this.#inputController.maybeGenerateSessionTitle(text);
|
|
2647
|
+
}
|
|
2648
|
+
|
|
2645
2649
|
handleImagePaste(): Promise<boolean> {
|
|
2646
2650
|
return this.#inputController.handleImagePaste();
|
|
2647
2651
|
}
|
|
@@ -11,6 +11,8 @@ import { ThinkingLevel } from "@gajae-code/agent-core";
|
|
|
11
11
|
import { runExtensionCompact, runExtensionSetModel } from "../extensibility/extensions/compact-handler";
|
|
12
12
|
import { getSessionSlashCommands } from "../extensibility/extensions/get-commands-handler";
|
|
13
13
|
import type { ExtensionError, ExtensionUIContext } from "../extensibility/extensions/types";
|
|
14
|
+
import { buildSessionProjectProgress } from "../progress/collect-project-progress";
|
|
15
|
+
import { toProjectProgressSnapshot } from "../progress/progress-snapshot";
|
|
14
16
|
import {
|
|
15
17
|
parseSyntheticModelId,
|
|
16
18
|
resolveSyntheticModelSelection,
|
|
@@ -201,6 +203,8 @@ export async function initializeExtensions(session: AgentSession, options: Initi
|
|
|
201
203
|
return { bytes: new Uint8Array(await file.slice(start, end).arrayBuffer()), totalBytes: file.size };
|
|
202
204
|
},
|
|
203
205
|
getJobs: () => session.getAsyncJobSnapshot(),
|
|
206
|
+
getProjectProgress: async () =>
|
|
207
|
+
toProjectProgressSnapshot(await buildSessionProjectProgress(session, session.sessionManager)),
|
|
204
208
|
onJobFold: listener => session.onJobFold(listener),
|
|
205
209
|
onSessionEvent: listener => session.subscribe(listener),
|
|
206
210
|
setSdkPermissionProvider: provider => session.setSdkPermissionProvider(provider),
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { getStreamIdleTimeoutRemediationMs } from "@gajae-code/ai/utils/idle-iterator";
|
|
2
|
+
|
|
1
3
|
const STREAM_IDLE_TIMEOUT_PATTERN = /\bstream stalled while waiting for the next event\b/i;
|
|
2
4
|
const GENERIC_ABORT_PATTERN = /^Request was aborted\.?$/i;
|
|
3
5
|
const ABORT_DISPLAY_LABEL_PATTERN = /^(?:Operation aborted|Aborted after \d+ retry attempts?)(?::|$)/;
|
|
@@ -37,5 +39,5 @@ function normalizeAbortCause(errorMessage: string | undefined): string {
|
|
|
37
39
|
function streamIdleTimeoutHint(cause: string): string {
|
|
38
40
|
if (!STREAM_IDLE_TIMEOUT_PATTERN.test(cause)) return "";
|
|
39
41
|
const separator = /[.!?]$/.test(cause) ? " " : ". ";
|
|
40
|
-
return `${separator}Hint: set PI_STREAM_IDLE_TIMEOUT_MS
|
|
42
|
+
return `${separator}Hint: set PI_STREAM_IDLE_TIMEOUT_MS=${getStreamIdleTimeoutRemediationMs()} for slow reasoning/proxy streams, or PI_STREAM_IDLE_TIMEOUT_MS=0 to disable the watchdog.`;
|
|
41
43
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { InteractiveModeContext } from "../types";
|
|
2
|
+
|
|
3
|
+
/** Ordinary focused UI owns interrupt/back before background work does. */
|
|
4
|
+
export function focusedUiOwnsInterrupt(
|
|
5
|
+
ctx: Pick<InteractiveModeContext, "ui" | "editor" | "hookSelector" | "hookInput" | "hookEditor">,
|
|
6
|
+
): boolean {
|
|
7
|
+
const focused = ctx.ui.getFocusedComponent?.();
|
|
8
|
+
if (!focused?.handleInput || focused === ctx.editor) return false;
|
|
9
|
+
// Non-inline hook prompts deliberately retain workflow-level interruption.
|
|
10
|
+
if (focused === ctx.hookSelector) return ctx.hookSelector.hasActiveInlineInput();
|
|
11
|
+
return focused !== ctx.hookInput && focused !== ctx.hookEditor;
|
|
12
|
+
}
|
|
@@ -16,6 +16,10 @@ export function matchesAppInterrupt(data: string): boolean {
|
|
|
16
16
|
return matchesKey(data, "escape") || matchesKey(data, "esc");
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
export function matchesAppClear(data: string): boolean {
|
|
20
|
+
return getKeybindings().matches(data, "app.clear");
|
|
21
|
+
}
|
|
22
|
+
|
|
19
23
|
export function matchesSelectCancel(data: string): boolean {
|
|
20
24
|
return getKeybindings().matches(data, "tui.select.cancel");
|
|
21
25
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { KeyId } from "@gajae-code/tui";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Characters macOS composes for Option+<letter> on the US/ABC layout when the
|
|
5
|
+
* terminal sends Option as text instead of Alt/Meta. Dead-key letters (e, i, n,
|
|
6
|
+
* u) compose nothing on their own and are absent.
|
|
7
|
+
*/
|
|
8
|
+
const MACOS_OPTION_COMPOSED_KEYS: ReadonlyMap<string, KeyId> = new Map<string, KeyId>([
|
|
9
|
+
["å", "alt+a"],
|
|
10
|
+
["∫", "alt+b"],
|
|
11
|
+
["ç", "alt+c"],
|
|
12
|
+
["∂", "alt+d"],
|
|
13
|
+
["ƒ", "alt+f"],
|
|
14
|
+
["©", "alt+g"],
|
|
15
|
+
["˙", "alt+h"],
|
|
16
|
+
["∆", "alt+j"],
|
|
17
|
+
["˚", "alt+k"],
|
|
18
|
+
["¬", "alt+l"],
|
|
19
|
+
["µ", "alt+m"],
|
|
20
|
+
["ø", "alt+o"],
|
|
21
|
+
["π", "alt+p"],
|
|
22
|
+
["œ", "alt+q"],
|
|
23
|
+
["®", "alt+r"],
|
|
24
|
+
["ß", "alt+s"],
|
|
25
|
+
["†", "alt+t"],
|
|
26
|
+
["√", "alt+v"],
|
|
27
|
+
["∑", "alt+w"],
|
|
28
|
+
["≈", "alt+x"],
|
|
29
|
+
["¥", "alt+y"],
|
|
30
|
+
["Ω", "alt+z"],
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
/** The Option chord that composes `text` on the macOS US/ABC layout, if any. */
|
|
34
|
+
export function macosOptionChordForComposedText(text: string): KeyId | undefined {
|
|
35
|
+
return MACOS_OPTION_COMPOSED_KEYS.get(text);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Terminal-specific instruction for sending Option as Alt/Meta, when the terminal is known. */
|
|
39
|
+
export function macosOptionForwardingInstruction(terminalProgram: string | undefined): string | undefined {
|
|
40
|
+
const program = terminalProgram?.toLowerCase() ?? "";
|
|
41
|
+
if (program.includes("ghostty")) {
|
|
42
|
+
return "Ghostty: set macos-option-as-alt = true in its config, then reload the config or restart Ghostty.";
|
|
43
|
+
}
|
|
44
|
+
if (program === "apple_terminal") {
|
|
45
|
+
return "Terminal.app: enable Settings > Profiles > Keyboard > Use Option as Meta key.";
|
|
46
|
+
}
|
|
47
|
+
if (program === "iterm.app") {
|
|
48
|
+
return "iTerm2: set Settings > Profiles > Keys > Left and Right Option key to Esc+.";
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface UnforwardedOptionChordWarnerOptions {
|
|
54
|
+
platform: NodeJS.Platform;
|
|
55
|
+
terminalProgram: string | undefined;
|
|
56
|
+
formatKeyHint: (key: KeyId) => string;
|
|
57
|
+
showWarning: (message: string) => void;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Builds the composer callback that explains, once, why a bound Option shortcut
|
|
62
|
+
* produced a character instead of firing. Returns undefined off macOS, where the
|
|
63
|
+
* composition table does not apply.
|
|
64
|
+
*/
|
|
65
|
+
export function createUnforwardedOptionChordWarner(
|
|
66
|
+
options: UnforwardedOptionChordWarnerOptions,
|
|
67
|
+
): ((key: KeyId, text: string) => void) | undefined {
|
|
68
|
+
if (options.platform !== "darwin") return undefined;
|
|
69
|
+
let warned = false;
|
|
70
|
+
return (key, text) => {
|
|
71
|
+
if (warned) return;
|
|
72
|
+
warned = true;
|
|
73
|
+
const instruction =
|
|
74
|
+
macosOptionForwardingInstruction(options.terminalProgram) ??
|
|
75
|
+
"Configure the terminal to send Option as Alt/Meta (Esc+).";
|
|
76
|
+
options.showWarning(
|
|
77
|
+
`${options.formatKeyHint(key)} arrived as "${text}": the terminal is sending Option as text, so Option shortcuts cannot reach GJC. ${instruction} Or remap the shortcut to a Control chord in keybindings.json.`,
|
|
78
|
+
);
|
|
79
|
+
};
|
|
80
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import type { SubagentLifecycle } from "../async/job-manager";
|
|
2
|
+
import { readUltragoalPlan } from "../gjc-runtime/ultragoal-runtime";
|
|
3
|
+
import type { Goal, GoalModeState } from "../goals/state";
|
|
4
|
+
import type { SessionManager } from "../session/session-manager";
|
|
5
|
+
import {
|
|
6
|
+
normalizeWorkflowHudSummary,
|
|
7
|
+
readVisibleSkillActiveStateWithStatus,
|
|
8
|
+
type VisibleSkillActiveStateReadStatus,
|
|
9
|
+
} from "../skill-state/active-state";
|
|
10
|
+
import { findLatestTodoPhasesFromEntries, type TodoPhase } from "../tools/todo-write";
|
|
11
|
+
import type { ProgressDurableSource } from "./progress-contract";
|
|
12
|
+
import {
|
|
13
|
+
computeProjectProgress,
|
|
14
|
+
type ProgressUltragoalInput,
|
|
15
|
+
type ProgressWorkflowInput,
|
|
16
|
+
type ProjectProgressInput,
|
|
17
|
+
type ProjectProgressReport,
|
|
18
|
+
} from "./project-progress";
|
|
19
|
+
|
|
20
|
+
/** Session-owned state the overview reads; everything else comes from durable `.gjc` files. */
|
|
21
|
+
export interface ProjectProgressSessionView {
|
|
22
|
+
cwd: string;
|
|
23
|
+
/** GJC session id that scopes `.gjc/_session-{id}` state; without it no workflow state is read. */
|
|
24
|
+
sessionId: string | undefined;
|
|
25
|
+
goal: Goal | undefined;
|
|
26
|
+
todoPhases: readonly TodoPhase[];
|
|
27
|
+
subagents: readonly SubagentLifecycle[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function readWorkflows(
|
|
31
|
+
cwd: string,
|
|
32
|
+
sessionId: string,
|
|
33
|
+
): Promise<{ status: VisibleSkillActiveStateReadStatus; workflows: ProgressWorkflowInput[] }> {
|
|
34
|
+
// The tolerant HUD reader maps an unreadable session snapshot to "nothing
|
|
35
|
+
// active" and lets snapshot-only rows survive; this projection must report
|
|
36
|
+
// unreadable state as unreadable, and never report a row without authority.
|
|
37
|
+
const { status, state } = await readVisibleSkillActiveStateWithStatus(cwd, sessionId);
|
|
38
|
+
const workflows = (state?.active_skills ?? [])
|
|
39
|
+
.filter(entry => entry.active !== false)
|
|
40
|
+
.map(entry => {
|
|
41
|
+
const hud = normalizeWorkflowHudSummary(entry.hud);
|
|
42
|
+
return {
|
|
43
|
+
skill: entry.skill,
|
|
44
|
+
phase: entry.phase?.trim() || "unknown",
|
|
45
|
+
...(hud?.summary ? { summary: hud.summary } : {}),
|
|
46
|
+
chips: (hud?.chips ?? []).map(chip => ({
|
|
47
|
+
label: chip.label,
|
|
48
|
+
...(chip.value ? { value: chip.value } : {}),
|
|
49
|
+
...(chip.severity ? { severity: chip.severity } : {}),
|
|
50
|
+
})),
|
|
51
|
+
};
|
|
52
|
+
});
|
|
53
|
+
return { status, workflows };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function readUltragoal(cwd: string, sessionId: string): Promise<ProgressUltragoalInput | undefined> {
|
|
57
|
+
const plan = await readUltragoalPlan(cwd, sessionId);
|
|
58
|
+
if (!plan || plan.goals.length === 0) return undefined;
|
|
59
|
+
return {
|
|
60
|
+
objective: plan.gjcObjective,
|
|
61
|
+
stories: plan.goals.map(goal => ({
|
|
62
|
+
id: goal.id,
|
|
63
|
+
title: goal.title,
|
|
64
|
+
status: goal.status,
|
|
65
|
+
hasVerificationReceipt: typeof goal.completionVerification?.receiptId === "string",
|
|
66
|
+
})),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Gather progress input strictly by reading state. A source that fails to read
|
|
72
|
+
* is reported as unreadable and excluded; it never aborts the overview and is
|
|
73
|
+
* never replaced by a guess. A source rebuilt from authoritative per-entry
|
|
74
|
+
* records after its snapshot failed to read is reported as recovered; one whose
|
|
75
|
+
* readable derived snapshot listed stale rows is reported as discarded; one whose
|
|
76
|
+
* rows could not be attributed to an authority is reported as unresolved.
|
|
77
|
+
*/
|
|
78
|
+
export async function collectProjectProgressInput(view: ProjectProgressSessionView): Promise<ProjectProgressInput> {
|
|
79
|
+
const unreadable: ProgressDurableSource[] = [];
|
|
80
|
+
const recovered: ProgressDurableSource[] = [];
|
|
81
|
+
const discarded: ProgressDurableSource[] = [];
|
|
82
|
+
const unresolved: ProgressDurableSource[] = [];
|
|
83
|
+
const sessionId = view.sessionId?.trim() || undefined;
|
|
84
|
+
let workflows: ProgressWorkflowInput[] = [];
|
|
85
|
+
let ultragoal: ProgressUltragoalInput | undefined;
|
|
86
|
+
if (sessionId) {
|
|
87
|
+
const [workflowResult, ultragoalResult] = await Promise.allSettled([
|
|
88
|
+
readWorkflows(view.cwd, sessionId),
|
|
89
|
+
readUltragoal(view.cwd, sessionId),
|
|
90
|
+
]);
|
|
91
|
+
if (workflowResult.status === "rejected" || workflowResult.value.status === "failed") {
|
|
92
|
+
unreadable.push("workflow-state");
|
|
93
|
+
} else {
|
|
94
|
+
workflows = workflowResult.value.workflows;
|
|
95
|
+
if (workflowResult.value.status === "recovered") recovered.push("workflow-state");
|
|
96
|
+
else if (workflowResult.value.status === "discarded") discarded.push("workflow-state");
|
|
97
|
+
else if (workflowResult.value.status === "unresolved") unresolved.push("workflow-state");
|
|
98
|
+
}
|
|
99
|
+
if (ultragoalResult.status === "fulfilled") ultragoal = ultragoalResult.value;
|
|
100
|
+
else unreadable.push("ultragoal-plan");
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
...(view.goal
|
|
104
|
+
? {
|
|
105
|
+
goal: {
|
|
106
|
+
objective: view.goal.objective,
|
|
107
|
+
status: view.goal.status,
|
|
108
|
+
timeUsedSeconds: view.goal.timeUsedSeconds,
|
|
109
|
+
},
|
|
110
|
+
}
|
|
111
|
+
: {}),
|
|
112
|
+
todos: view.todoPhases.flatMap(phase =>
|
|
113
|
+
phase.tasks.map(task => ({ content: task.content, status: task.status })),
|
|
114
|
+
),
|
|
115
|
+
workflows,
|
|
116
|
+
...(ultragoal ? { ultragoal } : {}),
|
|
117
|
+
subagents: [...view.subagents],
|
|
118
|
+
sessionStateRead: sessionId !== undefined,
|
|
119
|
+
unreadable,
|
|
120
|
+
recovered,
|
|
121
|
+
discarded,
|
|
122
|
+
unresolved,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** The live session state `/progress` and `session.progress` read; satisfied by `AgentSession`. */
|
|
127
|
+
export interface ProjectProgressSessionSource {
|
|
128
|
+
getGoalModeState(): GoalModeState | undefined;
|
|
129
|
+
getTodoPhases(): TodoPhase[];
|
|
130
|
+
getSubagentLifecycleStatuses(): SubagentLifecycle[];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Single entry point shared by the human view and the machine snapshot.
|
|
135
|
+
*
|
|
136
|
+
* Todos come from the persisted branch history, as `/todo` reads them: the live
|
|
137
|
+
* list drops completed and abandoned items when a session is resumed, so it is
|
|
138
|
+
* only the fallback when no todo state was ever persisted. A persisted empty
|
|
139
|
+
* list is authoritative and is not replaced by live state.
|
|
140
|
+
*/
|
|
141
|
+
export async function buildSessionProjectProgress(
|
|
142
|
+
session: ProjectProgressSessionSource,
|
|
143
|
+
sessionManager: Pick<SessionManager, "getCwd" | "getSessionId" | "getBranch">,
|
|
144
|
+
): Promise<ProjectProgressReport> {
|
|
145
|
+
const input = await collectProjectProgressInput({
|
|
146
|
+
cwd: sessionManager.getCwd(),
|
|
147
|
+
sessionId: sessionManager.getSessionId(),
|
|
148
|
+
goal: session.getGoalModeState()?.goal,
|
|
149
|
+
todoPhases: findLatestTodoPhasesFromEntries(sessionManager.getBranch()) ?? session.getTodoPhases(),
|
|
150
|
+
subagents: session.getSubagentLifecycleStatuses(),
|
|
151
|
+
});
|
|
152
|
+
return computeProjectProgress(input);
|
|
153
|
+
}
|