@gajae-code/coding-agent 0.14.2 → 0.15.0
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 +120 -1
- package/dist/types/async/job-manager.d.ts +4 -1
- package/dist/types/autoresearch/dashboard.d.ts +29 -4
- package/dist/types/autoresearch/data-context.d.ts +25 -0
- package/dist/types/autoresearch/git.d.ts +65 -7
- package/dist/types/autoresearch/harness.d.ts +58 -0
- package/dist/types/autoresearch/prompts.d.ts +51 -0
- package/dist/types/autoresearch/runs.d.ts +173 -0
- package/dist/types/autoresearch/types.d.ts +9 -139
- package/dist/types/capability/types.d.ts +20 -0
- package/dist/types/cli/accounts-cli.d.ts +1 -0
- package/dist/types/commands/{rlm.d.ts → autoresearch.d.ts} +2 -4
- package/dist/types/config/autorouting-contract.d.ts +146 -0
- package/dist/types/config/autorouting-generator.d.ts +40 -0
- package/dist/types/config/autorouting-tier-map.d.ts +16844 -0
- package/dist/types/config/autorouting.d.ts +35 -0
- package/dist/types/config/model-discovery-manager.d.ts +8 -1
- package/dist/types/config/model-registry.d.ts +30 -1
- package/dist/types/config/provider-selection-policy.d.ts +23 -0
- package/dist/types/config/settings-schema.d.ts +61 -3
- package/dist/types/config/settings.d.ts +3 -0
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/coordinator-mcp/durability.d.ts +29 -0
- package/dist/types/coordinator-mcp/event-webhook.d.ts +50 -0
- package/dist/types/coordinator-mcp/question-state.d.ts +1 -1
- package/dist/types/coordinator-mcp/server.d.ts +14 -0
- package/dist/types/crash/index-store.d.ts +3 -0
- package/dist/types/crash/record-loader.d.ts +2 -0
- package/dist/types/crash/upstream/relay.d.ts +5 -3
- package/dist/types/cursor.d.ts +12 -0
- package/dist/types/defaults/gjc-defaults.d.ts +35 -1
- package/dist/types/defaults/gjc-skills.generated.d.ts +1 -1
- package/dist/types/discovery/helpers.d.ts +1 -1
- package/dist/types/edit/index.d.ts +2 -1
- package/dist/types/eval/py/executor.d.ts +2 -0
- package/dist/types/extensibility/extensions/loader.d.ts +32 -0
- package/dist/types/extensibility/gjc-plugins/types.d.ts +1 -1
- package/dist/types/extensibility/skills.d.ts +2 -0
- package/dist/types/gjc-runtime/autoresearch-runtime.d.ts +194 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +1 -1
- package/dist/types/gjc-runtime/psmux-detect.d.ts +1 -1
- package/dist/types/gjc-runtime/python-transcript.d.ts +19 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +2 -8
- package/dist/types/gjc-runtime/session-layout.d.ts +6 -3
- package/dist/types/gjc-runtime/state-renderer.d.ts +0 -22
- package/dist/types/gjc-runtime/state-schema.d.ts +7 -7
- package/dist/types/gjc-runtime/tmux-common.d.ts +2 -2
- package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +38 -1
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +2 -24
- package/dist/types/gjc-runtime/ultragoal-validation-policy.d.ts +73 -0
- package/dist/types/gjc-runtime/workflow-recovery-projection.d.ts +100 -0
- package/dist/types/hooks/skill-keywords.d.ts +1 -1
- package/dist/types/main.d.ts +0 -10
- package/dist/types/modes/acp/acp-agent.d.ts +13 -6
- package/dist/types/modes/components/model-selector.d.ts +14 -0
- package/dist/types/modes/components/skill-hud/render.d.ts +6 -1
- package/dist/types/modes/components/smart-routing-panel.d.ts +82 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +14 -0
- package/dist/types/modes/interactive-mode.d.ts +3 -0
- package/dist/types/modes/types.d.ts +9 -0
- package/dist/types/rlm/artifacts.d.ts +4 -4
- package/dist/types/runtime/optional-runtime-services.d.ts +2 -1
- package/dist/types/runtime/workspace-tree-service.d.ts +5 -1
- package/dist/types/runtime-mcp/config.d.ts +6 -0
- package/dist/types/runtime-mcp/manager.d.ts +10 -0
- package/dist/types/sdk/acp/adapter.d.ts +0 -4
- package/dist/types/sdk/broker/broker.d.ts +1 -1
- package/dist/types/sdk/broker/lifecycle.d.ts +57 -2
- package/dist/types/sdk/broker/session-index.d.ts +15 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +6 -0
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +4 -0
- package/dist/types/sdk/bus/index.d.ts +8 -0
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +2 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +12 -2
- package/dist/types/sdk/client/client.d.ts +39 -4
- package/dist/types/sdk/host/host.d.ts +1 -0
- package/dist/types/sdk/host/internal-autorouting-state.d.ts +3 -0
- package/dist/types/sdk/host/model-pin.d.ts +29 -0
- package/dist/types/sdk/host/session-runtime.d.ts +2 -1
- package/dist/types/sdk/lifecycle-session.d.ts +13 -0
- package/dist/types/sdk/router/session-router.d.ts +27 -1
- package/dist/types/sdk/session.d.ts +11 -1
- package/dist/types/session/agent-session.d.ts +36 -6
- package/dist/types/session/artifacts.d.ts +16 -1
- package/dist/types/session/internal/managed-session-storage.d.ts +7 -0
- package/dist/types/session/session-manager.d.ts +84 -2
- package/dist/types/session/session-staging-paths.d.ts +3 -0
- package/dist/types/skill-state/canonical-skills.d.ts +1 -1
- package/dist/types/skill-state/workflow-hud.d.ts +12 -21
- package/dist/types/skill-state/workflow-mutation-guard.d.ts +1 -0
- package/dist/types/slash-commands/helpers/autorouting-status.d.ts +8 -0
- package/dist/types/system-prompt.d.ts +23 -0
- package/dist/types/task/executor.d.ts +35 -4
- package/dist/types/task/index.d.ts +17 -3
- package/dist/types/task/receipt.d.ts +3 -1
- package/dist/types/task/types.d.ts +144 -0
- package/dist/types/tools/atomic-file-write.d.ts +72 -0
- package/dist/types/tools/descriptors.d.ts +1 -0
- package/dist/types/tools/index.d.ts +36 -1
- package/dist/types/tools/move-session.d.ts +40 -0
- package/dist/types/tools/python.d.ts +12 -0
- package/dist/types/tools/resource-gc.d.ts +0 -3
- package/dist/types/tools/search.d.ts +8 -1
- package/dist/types/tools/skill.d.ts +9 -4
- package/dist/types/tools/subagent.d.ts +33 -3
- package/dist/types/utils/xml-escape.d.ts +2 -0
- package/dist/types/workflow/workflow-intent-diff.d.ts +2 -2
- package/dist/types/workflow/workflow-intent-report-consumer.d.ts +1 -1
- package/package.json +11 -12
- package/scripts/build-sdk-package-smoke.ts +7 -5
- package/scripts/check-autorouting-tier-map.ts +183 -0
- package/scripts/generate-sdk-operation-inventory.ts +12 -0
- package/scripts/generate-tool-catalog.ts +40 -5
- package/src/async/job-manager.ts +5 -2
- package/src/autoresearch/dashboard.ts +134 -314
- package/src/autoresearch/data-context.ts +35 -0
- package/src/autoresearch/git.ts +186 -26
- package/src/autoresearch/{helpers.ts → harness.ts} +65 -40
- package/src/autoresearch/{prompt-setup.md → prompts/prompt-setup.md} +7 -7
- package/src/autoresearch/prompts/prompt.md +103 -0
- package/src/autoresearch/prompts.ts +126 -0
- package/src/autoresearch/runs.ts +708 -0
- package/src/autoresearch/types.ts +9 -150
- package/src/capability/index.ts +4 -4
- package/src/capability/types.ts +20 -0
- package/src/cli/accounts-cli.ts +21 -5
- package/src/cli/config-cli.ts +25 -1
- package/src/cli/fast-help.ts +1 -2
- package/src/cli/list-models.ts +5 -8
- package/src/cli/setup-cli.ts +8 -0
- package/src/cli.ts +2 -21
- package/src/commands/autoresearch.ts +33 -0
- package/src/commands/coordinator.ts +15 -0
- package/src/commands/launch.ts +2 -4
- package/src/commands/quick-lane.ts +1 -1
- package/src/commands/sdk.ts +24 -0
- package/src/commands/state.ts +1 -1
- package/src/config/autorouting-contract.ts +474 -0
- package/src/config/autorouting-generator.ts +238 -0
- package/src/config/autorouting-tier-map.ts +6108 -0
- package/src/config/autorouting.ts +120 -0
- package/src/config/model-discovery-manager.ts +42 -11
- package/src/config/model-registry.ts +348 -68
- package/src/config/provider-selection-policy.ts +60 -9
- package/src/config/settings-schema.ts +195 -6
- package/src/config/settings.ts +62 -1
- package/src/config.ts +2 -3
- package/src/coordinator/contract.ts +0 -1
- package/src/coordinator-mcp/codex-handoff.ts +68 -42
- package/src/coordinator-mcp/durability.ts +200 -0
- package/src/coordinator-mcp/event-webhook.ts +278 -0
- package/src/coordinator-mcp/question-state.ts +109 -46
- package/src/coordinator-mcp/server.ts +932 -504
- package/src/crash/index-store.ts +126 -22
- package/src/crash/record-loader.ts +7 -0
- package/src/crash/upstream/relay.ts +82 -34
- package/src/cursor.ts +84 -1
- package/src/debug/index.ts +26 -0
- package/src/defaults/gjc/skills/autoresearch/SKILL.md +116 -0
- package/src/defaults/gjc/skills/autoresearch/auto-critic.md +46 -0
- package/src/defaults/gjc/skills/autoresearch/auto-iterate.md +42 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +17 -37
- package/src/defaults/gjc/skills/ralplan/SKILL.md +21 -18
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +6 -17
- package/src/defaults/gjc/skills/ultragoal/ai-slop-cleaner.md +2 -2
- package/src/defaults/gjc-defaults.ts +89 -2
- package/src/defaults/gjc-skills.generated.ts +24 -17
- package/src/discovery/builtin.ts +18 -5
- package/src/discovery/helpers.ts +6 -6
- package/src/edit/index.ts +5 -2
- package/src/eval/py/executor.ts +8 -0
- package/src/extensibility/extensions/loader.ts +135 -13
- package/src/extensibility/extensions/wrapper.ts +1 -2
- package/src/extensibility/gjc-plugins/schema.ts +1 -1
- package/src/extensibility/runtime-skill-discovery.ts +2 -2
- package/src/extensibility/skill-management.ts +2 -3
- package/src/extensibility/skills.ts +16 -8
- package/src/gjc-runtime/autoresearch-runtime.ts +1509 -0
- package/src/gjc-runtime/gc-render.ts +0 -1
- package/src/gjc-runtime/gc-runtime.ts +1 -11
- package/src/gjc-runtime/launch-tmux.ts +3 -3
- package/src/gjc-runtime/psmux-detect.ts +2 -2
- package/src/gjc-runtime/python-transcript.ts +58 -0
- package/src/gjc-runtime/ralplan-runtime.ts +6 -18
- package/src/gjc-runtime/session-layout.ts +18 -7
- package/src/gjc-runtime/session-state-sidecar.ts +58 -9
- package/src/gjc-runtime/state-renderer.ts +0 -38
- package/src/gjc-runtime/state-runtime.ts +43 -22
- package/src/gjc-runtime/state-schema.ts +1 -1
- package/src/gjc-runtime/tmux-common.ts +7 -7
- package/src/gjc-runtime/ultragoal-change-set.ts +232 -11
- package/src/gjc-runtime/ultragoal-runtime.ts +309 -114
- package/src/gjc-runtime/ultragoal-validation-policy.ts +247 -0
- package/src/gjc-runtime/workflow-command-ref.ts +15 -11
- package/src/gjc-runtime/workflow-manifest.generated.json +413 -532
- package/src/gjc-runtime/workflow-manifest.ts +43 -83
- package/src/gjc-runtime/workflow-recovery-projection.ts +616 -0
- package/src/hooks/skill-keywords.ts +3 -3
- package/src/hooks/skill-state.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +26 -24
- package/src/lsp/index.ts +25 -5
- package/src/main.ts +2 -27
- package/src/modes/acp/acp-agent.ts +200 -75
- package/src/modes/components/model-selector.ts +216 -13
- package/src/modes/components/oauth-selector.ts +5 -5
- package/src/modes/components/skill-hud/render.ts +173 -35
- package/src/modes/components/smart-routing-panel.ts +531 -0
- package/src/modes/components/tool-status-header.ts +6 -3
- package/src/modes/controllers/command-controller.ts +17 -7
- package/src/modes/controllers/input-controller.ts +44 -9
- package/src/modes/controllers/runtime-mcp-command-controller.ts +4 -0
- package/src/modes/controllers/selector-controller.ts +298 -65
- package/src/modes/interactive-mode.ts +8 -1
- package/src/modes/shared/agent-wire/workflow-gate-types.ts +1 -1
- package/src/modes/types.ts +9 -1
- package/src/prompts/agents/planner.md +1 -1
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/prompts/system/volatile-project-context.md +2 -1
- package/src/prompts/tools/move-session.md +15 -0
- package/src/prompts/tools/python.md +12 -0
- package/src/prompts/tools/skill-discovery.md +1 -1
- package/src/prompts/tools/skill.md +2 -2
- package/src/prompts/tools/task-summary.md +1 -0
- package/src/prompts/tools/task.md +6 -0
- package/src/rlm/artifacts.ts +12 -10
- package/src/runtime/optional-runtime-services.ts +3 -2
- package/src/runtime/workspace-tree-service.ts +11 -3
- package/src/runtime-mcp/config.ts +8 -1
- package/src/runtime-mcp/manager.ts +33 -0
- package/src/sdk/acp/adapter.ts +48 -1
- package/src/sdk/broker/broker.ts +30 -2
- package/src/sdk/broker/discovery.ts +217 -2
- package/src/sdk/broker/lifecycle.ts +385 -59
- package/src/sdk/broker/session-index.ts +243 -48
- package/src/sdk/broker/transport.ts +1 -0
- package/src/sdk/bus/chat-daemon-control.ts +8 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +5 -0
- package/src/sdk/bus/index.ts +208 -11
- package/src/sdk/bus/kind-aware-reconciliation.ts +16 -0
- package/src/sdk/bus/reconciliation-store.ts +27 -5
- package/src/sdk/bus/telegram-daemon-contract.ts +12 -2
- package/src/sdk/client/client.ts +204 -22
- package/src/sdk/host/host.ts +16 -0
- package/src/sdk/host/internal-autorouting-state.ts +13 -0
- package/src/sdk/host/model-pin.ts +101 -0
- package/src/sdk/host/reverse-leases.ts +2 -1
- package/src/sdk/host/session-runtime.test.ts +224 -1
- package/src/sdk/host/session-runtime.ts +125 -37
- package/src/sdk/lifecycle/service.ts +3 -1
- package/src/sdk/lifecycle-session.ts +42 -1
- package/src/sdk/protocol/operation-inventory.generated.json +66 -0
- package/src/sdk/router/session-router.ts +375 -36
- package/src/sdk/session.ts +547 -47
- package/src/session/account-inventory.ts +1 -1
- package/src/session/agent-session.ts +765 -189
- package/src/session/artifacts.ts +234 -4
- package/src/session/internal/managed-session-storage.ts +91 -4
- package/src/session/provider-safety-stop-hint.ts +3 -2
- package/src/session/session-manager.ts +775 -16
- package/src/session/session-staging-paths.ts +8 -0
- package/src/setup/hermes/templates/operator-instructions.v1.md +0 -1
- package/src/setup/host-plugin-setup.ts +2 -2
- package/src/skill-state/active-state.ts +2 -2
- package/src/skill-state/canonical-skills.ts +1 -1
- package/src/skill-state/initial-phase.ts +1 -1
- package/src/skill-state/workflow-hud.ts +41 -41
- package/src/skill-state/workflow-mutation-guard.ts +26 -7
- package/src/slash-commands/builtin-registry.ts +77 -0
- package/src/slash-commands/helpers/autorouting-status.ts +55 -0
- package/src/system-prompt.ts +102 -5
- package/src/task/discovery.ts +2 -3
- package/src/task/executor.ts +617 -43
- package/src/task/index.ts +262 -15
- package/src/task/receipt.ts +30 -0
- package/src/task/render.ts +11 -1
- package/src/task/types.ts +316 -0
- package/src/tools/atomic-file-write.ts +594 -0
- package/src/tools/bisect.ts +3 -2
- package/src/tools/descriptors.test.ts +3 -0
- package/src/tools/descriptors.ts +24 -0
- package/src/tools/image-gen.ts +5 -1
- package/src/tools/index.ts +62 -5
- package/src/tools/move-session.ts +107 -0
- package/src/tools/python.ts +205 -0
- package/src/tools/read.ts +88 -15
- package/src/tools/renderers.ts +2 -0
- package/src/tools/resource-gc.ts +8 -177
- package/src/tools/search.ts +30 -6
- package/src/tools/skill-discovery.ts +1 -0
- package/src/tools/skill.ts +37 -7
- package/src/tools/subagent-render.ts +119 -13
- package/src/tools/subagent.ts +91 -74
- package/src/tools/tool-catalog.generated.ts +64 -2
- package/src/tools/write.ts +16 -12
- package/src/utils/xml-escape.ts +14 -0
- package/src/web/search/providers/codex.ts +2 -2
- package/src/workflow/workflow-intent-diff.ts +13 -12
- package/src/workflow/workflow-intent-report-consumer.ts +6 -6
- package/dist/types/autoresearch/helpers.d.ts +0 -24
- package/dist/types/autoresearch/index.d.ts +0 -2
- package/dist/types/autoresearch/state.d.ts +0 -17
- package/dist/types/autoresearch/storage.d.ts +0 -142
- package/dist/types/autoresearch/tools/init-experiment.d.ts +0 -31
- package/dist/types/autoresearch/tools/log-experiment.d.ts +0 -23
- package/dist/types/autoresearch/tools/run-experiment.d.ts +0 -8
- package/dist/types/autoresearch/tools/update-notes.d.ts +0 -12
- package/dist/types/commands/team.d.ts +0 -24
- package/dist/types/gjc-runtime/team-gc.d.ts +0 -7
- package/dist/types/gjc-runtime/team-launch.d.ts +0 -61
- package/dist/types/gjc-runtime/team-notify.d.ts +0 -45
- package/dist/types/gjc-runtime/team-runtime.d.ts +0 -336
- package/dist/types/gjc-runtime/team-store.d.ts +0 -168
- package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +0 -43
- package/dist/types/gjc-runtime/team-worker-memory-guard.d.ts +0 -92
- package/dist/types/gjc-runtime/team-workers.d.ts +0 -83
- package/dist/types/rlm/index.d.ts +0 -47
- package/dist/types/rlm/preset.d.ts +0 -23
- package/dist/types/rlm/python-tool.d.ts +0 -16
- package/src/autoresearch/command-resume.md +0 -14
- package/src/autoresearch/index.ts +0 -536
- package/src/autoresearch/prompt.md +0 -103
- package/src/autoresearch/resume-message.md +0 -10
- package/src/autoresearch/state.ts +0 -273
- package/src/autoresearch/storage.ts +0 -699
- package/src/autoresearch/tools/init-experiment.ts +0 -272
- package/src/autoresearch/tools/log-experiment.ts +0 -524
- package/src/autoresearch/tools/run-experiment.ts +0 -475
- package/src/autoresearch/tools/update-notes.ts +0 -109
- package/src/commands/rlm.ts +0 -19
- package/src/commands/team.ts +0 -238
- package/src/defaults/gjc/skills/deep-interview/auto-research-greenfield.md +0 -42
- package/src/defaults/gjc/skills/team/SKILL.md +0 -467
- package/src/gjc-runtime/team-gc.ts +0 -49
- package/src/gjc-runtime/team-launch.ts +0 -404
- package/src/gjc-runtime/team-notify.ts +0 -326
- package/src/gjc-runtime/team-runtime.ts +0 -5923
- package/src/gjc-runtime/team-store.ts +0 -878
- package/src/gjc-runtime/team-worker-heartbeat.ts +0 -149
- package/src/gjc-runtime/team-worker-memory-guard.ts +0 -363
- package/src/gjc-runtime/team-workers.ts +0 -1123
- package/src/prompts/system/rlm-report-command.md +0 -1
- package/src/prompts/system/rlm-research.md +0 -23
- package/src/rlm/index.ts +0 -365
- package/src/rlm/preset.ts +0 -76
- package/src/rlm/python-tool.ts +0 -68
package/src/tools/resource-gc.ts
CHANGED
|
@@ -18,15 +18,9 @@ function safeProbeWindowsJobMemory(): WindowsJobMemoryProbeResult {
|
|
|
18
18
|
|
|
19
19
|
import { logger } from "@gajae-code/utils";
|
|
20
20
|
import type { Settings } from "../config/settings";
|
|
21
|
-
import { executeGjcTeamApiOperation, listGjcTeams, readGjcWorkerHeartbeat } from "../gjc-runtime/team-runtime";
|
|
22
21
|
import { computeMemoryGuardDomain } from "../runtime/memory-domain";
|
|
23
|
-
import {
|
|
24
|
-
|
|
25
|
-
MemoryGuardHost,
|
|
26
|
-
resolveMemoryGuardPolicy,
|
|
27
|
-
revalidateMemoryGuardAction,
|
|
28
|
-
} from "../runtime/memory-guard";
|
|
29
|
-
import type { MemoryGuardPolicy, MemoryGuardWorkerSample } from "../runtime/memory-guard-contract";
|
|
22
|
+
import { chooseMemoryGuardAction, MemoryGuardHost, resolveMemoryGuardPolicy } from "../runtime/memory-guard";
|
|
23
|
+
import type { MemoryGuardPolicy } from "../runtime/memory-guard-contract";
|
|
30
24
|
import { resolveEffectiveMemoryLimit } from "../runtime/memory-limit";
|
|
31
25
|
import { listTabsForGc, releaseTabIfGcEligible, type TabGcSnapshot } from "./browser/tab-supervisor";
|
|
32
26
|
import { cleanupStaleScreenshotFallbackDirs, hasCreatedScreenshotFallbackDir } from "./computer-gc";
|
|
@@ -91,14 +85,6 @@ export interface ResourceGcDeps {
|
|
|
91
85
|
releaseTab: (name: string, policy: { now: () => number; idleMs: number }) => Promise<boolean>;
|
|
92
86
|
cleanupScreenshots: (opts: { now: () => number; staleMs: number }) => Promise<{ scanned: number; removed: number }>;
|
|
93
87
|
screenshotArmed: () => boolean;
|
|
94
|
-
listTeamWorkers?: (cwd: string, sessionId: string) => Promise<MemoryGuardWorkerSample[]>;
|
|
95
|
-
applyTeamWorkerGuard?: (
|
|
96
|
-
cwd: string,
|
|
97
|
-
sessionId: string,
|
|
98
|
-
workerId: string,
|
|
99
|
-
excessBytes: number,
|
|
100
|
-
incidentId: string,
|
|
101
|
-
) => Promise<void>;
|
|
102
88
|
}
|
|
103
89
|
|
|
104
90
|
/** Request collection without synchronously stopping the main event loop. */
|
|
@@ -117,9 +103,6 @@ const defaultDeps: ResourceGcDeps = {
|
|
|
117
103
|
releaseTab: (name, policy) => releaseTabIfGcEligible(name, policy),
|
|
118
104
|
cleanupScreenshots: opts => cleanupStaleScreenshotFallbackDirs(opts),
|
|
119
105
|
screenshotArmed: () => hasCreatedScreenshotFallbackDir(),
|
|
120
|
-
listTeamWorkers: (cwd, sessionId) => sampleTeamWorkers(cwd, sessionId),
|
|
121
|
-
applyTeamWorkerGuard: (cwd, sessionId, workerId, excessBytes, incidentId) =>
|
|
122
|
-
applySelectedTeamWorker(cwd, sessionId, workerId, excessBytes, incidentId),
|
|
123
106
|
};
|
|
124
107
|
|
|
125
108
|
// ── Controller state (process-global; tabs/browsers are module-global too) ──────────────────
|
|
@@ -135,8 +118,6 @@ let lastScreenshotScanAt = 0;
|
|
|
135
118
|
const memoryGuardGcActive = new Set<string>();
|
|
136
119
|
const memoryGuardLastEvaluatedAt = new Map<string, number>();
|
|
137
120
|
const memoryGuardRestartAboveSince = new Map<string, number>();
|
|
138
|
-
const memoryGuardRestartCooldownUntil = new Map<string, number>();
|
|
139
|
-
const memoryGuardWorkerIncidentIds = new Map<string, string>();
|
|
140
121
|
let deps: ResourceGcDeps = defaultDeps;
|
|
141
122
|
|
|
142
123
|
export interface ResourceGcRegistration {
|
|
@@ -175,8 +156,6 @@ export function registerResourceGcSession(reg: ResourceGcRegistration): () => vo
|
|
|
175
156
|
if (path === "memoryGuard.enabled" && !resolveMemoryGuardPolicy(reg.settings).enabled) {
|
|
176
157
|
memoryGuardGcActive.delete(reg.sessionId);
|
|
177
158
|
memoryGuardRestartAboveSince.delete(reg.sessionId);
|
|
178
|
-
memoryGuardWorkerIncidentIds.delete(reg.sessionId);
|
|
179
|
-
memoryGuardRestartCooldownUntil.delete(reg.sessionId);
|
|
180
159
|
memoryGuardLastEvaluatedAt.delete(reg.sessionId);
|
|
181
160
|
}
|
|
182
161
|
}
|
|
@@ -186,11 +165,9 @@ export function registerResourceGcSession(reg: ResourceGcRegistration): () => vo
|
|
|
186
165
|
if (unregistered) return;
|
|
187
166
|
unregistered = true;
|
|
188
167
|
activeSessions.delete(reg.sessionId);
|
|
189
|
-
memoryGuardLastEvaluatedAt.delete(reg.sessionId);
|
|
190
168
|
memoryGuardGcActive.delete(reg.sessionId);
|
|
191
169
|
memoryGuardRestartAboveSince.delete(reg.sessionId);
|
|
192
|
-
|
|
193
|
-
memoryGuardRestartCooldownUntil.delete(reg.sessionId);
|
|
170
|
+
memoryGuardLastEvaluatedAt.delete(reg.sessionId);
|
|
194
171
|
unregisterSchedule();
|
|
195
172
|
unregisterSettings();
|
|
196
173
|
};
|
|
@@ -514,105 +491,11 @@ function sweepMemoryPressureGuard(d: ResourceGcDeps): Promise<void> | undefined
|
|
|
514
491
|
}
|
|
515
492
|
memoryGuardGcActive.delete(sessionId);
|
|
516
493
|
memoryGuardRestartAboveSince.delete(sessionId);
|
|
517
|
-
memoryGuardWorkerIncidentIds.delete(sessionId);
|
|
518
|
-
memoryGuardRestartCooldownUntil.delete(sessionId);
|
|
519
494
|
memoryGuardLastEvaluatedAt.delete(sessionId);
|
|
520
495
|
}
|
|
521
496
|
if (!enabled) return undefined;
|
|
522
497
|
return sweepEnabledMemoryPressureGuard(d);
|
|
523
498
|
}
|
|
524
|
-
|
|
525
|
-
async function readLinuxWorkerRssBytes(pid: number): Promise<number | null> {
|
|
526
|
-
if (process.platform !== "linux" || !Number.isSafeInteger(pid) || pid <= 0) return null;
|
|
527
|
-
try {
|
|
528
|
-
const status = await Bun.file(`/proc/${pid}/status`).text();
|
|
529
|
-
const match = /^VmRSS:\s+(\d+)\s+kB$/m.exec(status);
|
|
530
|
-
if (!match) return null;
|
|
531
|
-
const kibibytes = Number(match[1]);
|
|
532
|
-
return Number.isSafeInteger(kibibytes) ? kibibytes * 1024 : null;
|
|
533
|
-
} catch {
|
|
534
|
-
return null;
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
async function readLinuxProcessStartTime(pid: number): Promise<string | null> {
|
|
539
|
-
try {
|
|
540
|
-
const stat = await Bun.file(`/proc/${pid}/stat`).text();
|
|
541
|
-
const commandEnd = stat.lastIndexOf(")");
|
|
542
|
-
if (commandEnd < 0) return null;
|
|
543
|
-
const startTime = stat
|
|
544
|
-
.slice(commandEnd + 2)
|
|
545
|
-
.trim()
|
|
546
|
-
.split(/\s+/)[19];
|
|
547
|
-
return startTime && /^\d+$/.test(startTime) ? startTime : null;
|
|
548
|
-
} catch {
|
|
549
|
-
return null;
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
async function sampleTeamWorkers(cwd: string, sessionId: string): Promise<MemoryGuardWorkerSample[]> {
|
|
554
|
-
if (process.platform !== "linux") return [];
|
|
555
|
-
const samples: MemoryGuardWorkerSample[] = [];
|
|
556
|
-
for (const team of await listGjcTeams(cwd, { ...process.env, GJC_SESSION_ID: sessionId })) {
|
|
557
|
-
if (team.phase === "complete" || team.phase === "cancelled") continue;
|
|
558
|
-
for (const worker of team.workers) {
|
|
559
|
-
try {
|
|
560
|
-
const heartbeat = await readGjcWorkerHeartbeat(team.team_name, worker.id, cwd, {
|
|
561
|
-
...process.env,
|
|
562
|
-
GJC_SESSION_ID: sessionId,
|
|
563
|
-
});
|
|
564
|
-
const heartbeatAt = Date.parse(heartbeat?.last_turn_at ?? "");
|
|
565
|
-
if (
|
|
566
|
-
!heartbeat?.alive ||
|
|
567
|
-
!heartbeat.process_start_time ||
|
|
568
|
-
!Number.isFinite(heartbeatAt) ||
|
|
569
|
-
Date.now() - heartbeatAt >= 120_000 ||
|
|
570
|
-
(await readLinuxProcessStartTime(heartbeat.pid)) !== heartbeat.process_start_time
|
|
571
|
-
)
|
|
572
|
-
continue;
|
|
573
|
-
const guard = (await executeGjcTeamApiOperation(
|
|
574
|
-
"read-worker-memory-guard",
|
|
575
|
-
{ team_name: team.team_name, worker: worker.id, platform: process.platform },
|
|
576
|
-
cwd,
|
|
577
|
-
{ ...process.env, GJC_SESSION_ID: sessionId },
|
|
578
|
-
)) as { automatic_action_allowed?: boolean; state?: string };
|
|
579
|
-
if (!guard.automatic_action_allowed || guard.state === "blocked") continue;
|
|
580
|
-
const bytes = await readLinuxWorkerRssBytes(heartbeat.pid);
|
|
581
|
-
if (bytes === null) continue;
|
|
582
|
-
samples.push({ workerId: `${team.team_name}/${worker.id}`, bytes, accepted: true });
|
|
583
|
-
} catch {
|
|
584
|
-
// Missing or malformed worker authority is not eligible for automatic mutation.
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
return samples;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
async function applySelectedTeamWorker(
|
|
592
|
-
cwd: string,
|
|
593
|
-
sessionId: string,
|
|
594
|
-
workerId: string,
|
|
595
|
-
excessBytes: number,
|
|
596
|
-
incidentId: string,
|
|
597
|
-
): Promise<void> {
|
|
598
|
-
const separator = workerId.lastIndexOf("/");
|
|
599
|
-
if (separator <= 0 || separator === workerId.length - 1) return;
|
|
600
|
-
const teamName = workerId.slice(0, separator);
|
|
601
|
-
const worker = workerId.slice(separator + 1);
|
|
602
|
-
await executeGjcTeamApiOperation(
|
|
603
|
-
"apply-worker-memory-guard",
|
|
604
|
-
{
|
|
605
|
-
team_name: teamName,
|
|
606
|
-
worker,
|
|
607
|
-
platform: process.platform,
|
|
608
|
-
reason: "production_memory_pressure_sweep",
|
|
609
|
-
incident_id: incidentId,
|
|
610
|
-
candidates: [{ worker_id: worker, platform: process.platform, excess_bytes: excessBytes }],
|
|
611
|
-
},
|
|
612
|
-
cwd,
|
|
613
|
-
{ ...process.env, GJC_SESSION_ID: sessionId },
|
|
614
|
-
);
|
|
615
|
-
}
|
|
616
499
|
async function sweepEnabledMemoryPressureGuard(d: ResourceGcDeps): Promise<void> {
|
|
617
500
|
const now = d.monotonicNow();
|
|
618
501
|
const dueSessions: Array<{ sessionId: string; policy: MemoryGuardPolicy; cwd: string }> = [];
|
|
@@ -622,8 +505,6 @@ async function sweepEnabledMemoryPressureGuard(d: ResourceGcDeps): Promise<void>
|
|
|
622
505
|
if (!policy.enabled) {
|
|
623
506
|
memoryGuardGcActive.delete(sessionId);
|
|
624
507
|
memoryGuardRestartAboveSince.delete(sessionId);
|
|
625
|
-
memoryGuardWorkerIncidentIds.delete(sessionId);
|
|
626
|
-
memoryGuardRestartCooldownUntil.delete(sessionId);
|
|
627
508
|
memoryGuardLastEvaluatedAt.delete(sessionId);
|
|
628
509
|
continue;
|
|
629
510
|
}
|
|
@@ -637,8 +518,7 @@ async function sweepEnabledMemoryPressureGuard(d: ResourceGcDeps): Promise<void>
|
|
|
637
518
|
const snapshot = await d.memorySnapshot();
|
|
638
519
|
let gcRequested = false;
|
|
639
520
|
const gcTelemetry: Array<{ sessionId: string } & Record<string, unknown>> = [];
|
|
640
|
-
|
|
641
|
-
for (const { sessionId, policy, cwd } of dueSessions) {
|
|
521
|
+
for (const { sessionId, policy } of dueSessions) {
|
|
642
522
|
const pressure = __selectMemoryPressureDomainForTest(snapshot, policy.policyLimitBytes);
|
|
643
523
|
const limit = resolveEffectiveMemoryLimit({
|
|
644
524
|
hardCapBytes: pressure.hardCapBytes,
|
|
@@ -647,23 +527,20 @@ async function sweepEnabledMemoryPressureGuard(d: ResourceGcDeps): Promise<void>
|
|
|
647
527
|
if (limit.effectiveBytes === null) {
|
|
648
528
|
memoryGuardGcActive.delete(sessionId);
|
|
649
529
|
memoryGuardRestartAboveSince.delete(sessionId);
|
|
650
|
-
|
|
651
|
-
memoryGuardRestartCooldownUntil.delete(sessionId);
|
|
530
|
+
memoryGuardLastEvaluatedAt.delete(sessionId);
|
|
652
531
|
continue;
|
|
653
532
|
}
|
|
654
|
-
const workerSamples = await (d.listTeamWorkers ?? (async () => []))(cwd, sessionId);
|
|
655
533
|
const domain = computeMemoryGuardDomain({
|
|
656
534
|
effectiveLimitBytes: limit.effectiveBytes,
|
|
657
535
|
totalUsageBytes: pressure.totalUsageBytes,
|
|
658
536
|
parentBytes: pressure.parentBytes,
|
|
659
537
|
parentReserveBytes: policy.parentReserveBytes,
|
|
660
|
-
workers:
|
|
538
|
+
workers: [],
|
|
661
539
|
});
|
|
662
540
|
const decision = chooseMemoryGuardAction({
|
|
663
541
|
domain,
|
|
664
542
|
hostSupported: false,
|
|
665
|
-
workerSupported:
|
|
666
|
-
workerSamples.some(worker => worker.workerId === workerId && worker.accepted !== false),
|
|
543
|
+
workerSupported: () => false,
|
|
667
544
|
});
|
|
668
545
|
const usageRatio = pressure.totalUsageBytes / limit.effectiveBytes;
|
|
669
546
|
if (usageRatio >= policy.gcThresholdRatio) {
|
|
@@ -686,56 +563,14 @@ async function sweepEnabledMemoryPressureGuard(d: ResourceGcDeps): Promise<void>
|
|
|
686
563
|
|
|
687
564
|
if (usageRatio < policy.restartThresholdRatio) {
|
|
688
565
|
memoryGuardRestartAboveSince.delete(sessionId);
|
|
689
|
-
memoryGuardWorkerIncidentIds.delete(sessionId);
|
|
690
566
|
continue;
|
|
691
567
|
}
|
|
692
568
|
const aboveSince = memoryGuardRestartAboveSince.get(sessionId);
|
|
693
569
|
if (aboveSince === undefined) {
|
|
694
570
|
memoryGuardRestartAboveSince.set(sessionId, now);
|
|
695
|
-
memoryGuardWorkerIncidentIds.set(sessionId, `worker-pressure:${sessionId}:${Math.trunc(now)}`);
|
|
696
571
|
continue;
|
|
697
572
|
}
|
|
698
|
-
|
|
699
|
-
if (now - aboveSince < policy.restartThresholdWindowMs || now < cooldownUntil) continue;
|
|
700
|
-
const workerIncidentId =
|
|
701
|
-
memoryGuardWorkerIncidentIds.get(sessionId) ?? `worker-pressure:${sessionId}:${Math.trunc(aboveSince)}`;
|
|
702
|
-
let workerActionAttemptedForSession = false;
|
|
703
|
-
if (decision.kind === "execute" && decision.target.kind === "worker" && !workerActionTaken) {
|
|
704
|
-
const refreshedWorkers = await (d.listTeamWorkers ?? (async () => []))(cwd, sessionId);
|
|
705
|
-
const refreshedDomain = computeMemoryGuardDomain({
|
|
706
|
-
effectiveLimitBytes: limit.effectiveBytes,
|
|
707
|
-
totalUsageBytes: pressure.totalUsageBytes,
|
|
708
|
-
parentBytes: pressure.parentBytes,
|
|
709
|
-
parentReserveBytes: policy.parentReserveBytes,
|
|
710
|
-
workers: refreshedWorkers,
|
|
711
|
-
});
|
|
712
|
-
const refreshedDecision = chooseMemoryGuardAction({
|
|
713
|
-
domain: refreshedDomain,
|
|
714
|
-
hostSupported: false,
|
|
715
|
-
workerSupported: workerId =>
|
|
716
|
-
refreshedWorkers.some(worker => worker.workerId === workerId && worker.accepted !== false),
|
|
717
|
-
});
|
|
718
|
-
const revalidated = revalidateMemoryGuardAction(decision, refreshedDecision);
|
|
719
|
-
if (revalidated.kind !== "execute" || revalidated.target.kind !== "worker") continue;
|
|
720
|
-
workerActionTaken = true;
|
|
721
|
-
workerActionAttemptedForSession = true;
|
|
722
|
-
try {
|
|
723
|
-
await (d.applyTeamWorkerGuard ?? (async () => undefined))(
|
|
724
|
-
cwd,
|
|
725
|
-
sessionId,
|
|
726
|
-
revalidated.target.workerId,
|
|
727
|
-
revalidated.target.excessBytes,
|
|
728
|
-
workerIncidentId,
|
|
729
|
-
);
|
|
730
|
-
} catch (error) {
|
|
731
|
-
d.logWarn("Memory guard: team worker action failed; continuing sweep", {
|
|
732
|
-
sessionId,
|
|
733
|
-
workerId: revalidated.target.workerId,
|
|
734
|
-
error: String(error),
|
|
735
|
-
});
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
if (workerActionAttemptedForSession) memoryGuardRestartCooldownUntil.set(sessionId, now + policy.cooldownMs);
|
|
573
|
+
if (now - aboveSince < policy.restartThresholdWindowMs) continue;
|
|
739
574
|
d.logWarn("Memory guard: restart threshold sustained", {
|
|
740
575
|
sessionId,
|
|
741
576
|
parentBytes: pressure.parentBytes,
|
|
@@ -864,8 +699,6 @@ export function __setResourceGcDepsForTest(overrides: Partial<ResourceGcDeps>):
|
|
|
864
699
|
...defaultDeps,
|
|
865
700
|
...overrides,
|
|
866
701
|
monotonicNow: overrides.monotonicNow ?? overrides.now ?? defaultDeps.monotonicNow,
|
|
867
|
-
listTeamWorkers: overrides.listTeamWorkers ?? (async () => []),
|
|
868
|
-
applyTeamWorkerGuard: overrides.applyTeamWorkerGuard ?? (async () => undefined),
|
|
869
702
|
};
|
|
870
703
|
}
|
|
871
704
|
|
|
@@ -917,8 +750,6 @@ export function __resetResourceGcForTest(): void {
|
|
|
917
750
|
rssWarningActive = false;
|
|
918
751
|
memoryGuardGcActive.clear();
|
|
919
752
|
memoryGuardRestartAboveSince.clear();
|
|
920
|
-
memoryGuardWorkerIncidentIds.clear();
|
|
921
|
-
memoryGuardRestartCooldownUntil.clear();
|
|
922
753
|
memoryGuardLastEvaluatedAt.clear();
|
|
923
754
|
lastScreenshotScanAt = 0;
|
|
924
755
|
deps = defaultDeps;
|
package/src/tools/search.ts
CHANGED
|
@@ -230,6 +230,13 @@ export interface SearchToolDetails {
|
|
|
230
230
|
missingPaths?: string[];
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
+
export interface SearchToolOptions {
|
|
234
|
+
/** Per-call symmetric context width used by Cursor's native Pi grep frame. */
|
|
235
|
+
context?: number;
|
|
236
|
+
/** Per-call total match cap used by Cursor's native Pi grep frame. */
|
|
237
|
+
totalMatchLimit?: number;
|
|
238
|
+
}
|
|
239
|
+
|
|
233
240
|
type SearchParams = z.infer<typeof searchSchema>;
|
|
234
241
|
|
|
235
242
|
export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDetails> {
|
|
@@ -247,7 +254,10 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
|
|
|
247
254
|
readonly parameters = searchSchema;
|
|
248
255
|
readonly strict = true;
|
|
249
256
|
|
|
250
|
-
constructor(
|
|
257
|
+
constructor(
|
|
258
|
+
private readonly session: ToolSession,
|
|
259
|
+
private readonly options: SearchToolOptions = {},
|
|
260
|
+
) {}
|
|
251
261
|
|
|
252
262
|
async execute(
|
|
253
263
|
_toolCallId: string,
|
|
@@ -293,8 +303,12 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
|
|
|
293
303
|
`or pass a UTF-8 text member.`,
|
|
294
304
|
);
|
|
295
305
|
}
|
|
296
|
-
const normalizedContextBefore = this.session.settings.get("search.contextBefore");
|
|
297
|
-
const normalizedContextAfter = this.session.settings.get("search.contextAfter");
|
|
306
|
+
const normalizedContextBefore = this.options.context ?? this.session.settings.get("search.contextBefore");
|
|
307
|
+
const normalizedContextAfter = this.options.context ?? this.session.settings.get("search.contextAfter");
|
|
308
|
+
const nativeMatchCap =
|
|
309
|
+
this.options.totalMatchLimit === undefined
|
|
310
|
+
? INTERNAL_TOTAL_CAP
|
|
311
|
+
: Math.min(INTERNAL_TOTAL_CAP, this.options.totalMatchLimit + 1);
|
|
298
312
|
const ignoreCase = i ?? false;
|
|
299
313
|
const useGitignore = gitignore ?? true;
|
|
300
314
|
const patternHasNewline = normalizedPattern.includes("\n") || normalizedPattern.includes("\\n");
|
|
@@ -362,7 +376,7 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
|
|
|
362
376
|
hidden: true,
|
|
363
377
|
gitignore: useGitignore,
|
|
364
378
|
cache: false,
|
|
365
|
-
maxCount:
|
|
379
|
+
maxCount: nativeMatchCap,
|
|
366
380
|
contextBefore: normalizedContextBefore,
|
|
367
381
|
contextAfter: normalizedContextAfter,
|
|
368
382
|
maxColumns: DEFAULT_MAX_COLUMN,
|
|
@@ -397,7 +411,7 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
|
|
|
397
411
|
hidden: true,
|
|
398
412
|
gitignore: useGitignore,
|
|
399
413
|
cache: false,
|
|
400
|
-
maxCount:
|
|
414
|
+
maxCount: nativeMatchCap,
|
|
401
415
|
contextBefore: normalizedContextBefore,
|
|
402
416
|
contextAfter: normalizedContextAfter,
|
|
403
417
|
maxColumns: DEFAULT_MAX_COLUMN,
|
|
@@ -469,6 +483,12 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
|
|
|
469
483
|
}
|
|
470
484
|
}
|
|
471
485
|
}
|
|
486
|
+
const explicitMatchLimitReached =
|
|
487
|
+
this.options.totalMatchLimit !== undefined &&
|
|
488
|
+
(selectedMatches.length > this.options.totalMatchLimit || result.limitReached);
|
|
489
|
+
if (this.options.totalMatchLimit !== undefined && selectedMatches.length > this.options.totalMatchLimit) {
|
|
490
|
+
selectedMatches.length = this.options.totalMatchLimit;
|
|
491
|
+
}
|
|
472
492
|
const nextSkip = skipFiles + windowFiles.length;
|
|
473
493
|
const limitMessage = fileLimitReached
|
|
474
494
|
? `Showing files ${skipFiles + 1}-${nextSkip} of ${totalFiles}. Use skip=${nextSkip} for the next page, or narrow paths/pattern.`
|
|
@@ -608,7 +628,11 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
|
|
|
608
628
|
})),
|
|
609
629
|
truncated,
|
|
610
630
|
fileLimitReached: fileLimitReached ? DEFAULT_FILE_LIMIT : undefined,
|
|
611
|
-
perFileLimitReached:
|
|
631
|
+
perFileLimitReached: explicitMatchLimitReached
|
|
632
|
+
? this.options.totalMatchLimit
|
|
633
|
+
: perFileLimitReached
|
|
634
|
+
? perFileMatchCap
|
|
635
|
+
: undefined,
|
|
612
636
|
displayContent: displayText,
|
|
613
637
|
missingPaths: missingPaths.length > 0 ? missingPaths : undefined,
|
|
614
638
|
};
|
|
@@ -78,6 +78,7 @@ export class SkillDiscoveryTool implements AgentTool<typeof skillDiscoverySchema
|
|
|
78
78
|
const source = input.source ?? "all";
|
|
79
79
|
const result = await discoverRuntimeSkills({
|
|
80
80
|
cwd: this.#session.cwd,
|
|
81
|
+
home: this.#session.home,
|
|
81
82
|
query: input.query,
|
|
82
83
|
source,
|
|
83
84
|
limit: input.limit,
|
package/src/tools/skill.ts
CHANGED
|
@@ -10,10 +10,15 @@
|
|
|
10
10
|
* project/user skills have no native workflow mode-state, so they are dispatched
|
|
11
11
|
* directly and tracked by the prompt observer instead.
|
|
12
12
|
*
|
|
13
|
-
* Canonical workflow chaining is refused unless the caller's
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
13
|
+
* Canonical workflow chaining is refused unless the caller's phase permits it:
|
|
14
|
+
* the generic terminal set `{complete, completed, handoff, failed, cancelled,
|
|
15
|
+
* canceled, inactive}`, the caller's manifest-declared terminal states (e.g.
|
|
16
|
+
* ralplan `final`), or — for autoresearch only — any phase with a declared
|
|
17
|
+
* handoff edge (`intake`/`research`/`verdict`): a research mission never
|
|
18
|
+
* mutates and guards no approval gate, its mission state is durable, so an
|
|
19
|
+
* ongoing or verdict-complete mission may hand off to planning at any point.
|
|
20
|
+
* ralplan/ultragoal keep the explicit write-to-handoff step for live phases:
|
|
21
|
+
* their mid-flight chaining is exactly what the approval-gate guard blocks.
|
|
17
22
|
*/
|
|
18
23
|
|
|
19
24
|
import type { AgentTool, AgentToolResult } from "@gajae-code/agent-core";
|
|
@@ -23,6 +28,7 @@ import { resolveSubskillActivationForSkillInvocation } from "../extensibility/gj
|
|
|
23
28
|
import { findRuntimeSkillByName } from "../extensibility/runtime-skill-discovery";
|
|
24
29
|
import { buildSkillPromptMessage } from "../extensibility/skills";
|
|
25
30
|
import { runNativeStateCommand } from "../gjc-runtime/state-runtime";
|
|
31
|
+
import { getSkillManifest } from "../gjc-runtime/workflow-manifest";
|
|
26
32
|
import skillDescription from "../prompts/tools/skill.md" with { type: "text" };
|
|
27
33
|
import { SKILL_PROMPT_MESSAGE_TYPE } from "../session/messages";
|
|
28
34
|
import { isCanonicalGjcWorkflowSkill } from "../skill-state/active-state";
|
|
@@ -31,6 +37,25 @@ import { ToolError } from "./tool-errors";
|
|
|
31
37
|
|
|
32
38
|
const TERMINAL_PHASES = new Set(["complete", "completed", "handoff", "failed", "cancelled", "canceled", "inactive"]);
|
|
33
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Whether the active canonical workflow's phase permits chaining into another
|
|
42
|
+
* skill. Generic terminal phases always chain; manifest terminal states cover
|
|
43
|
+
* skill-specific termini the generic set misses (ralplan "final"); and
|
|
44
|
+
* autoresearch chains from any phase with a manifest handoff edge because a
|
|
45
|
+
* research mission is safely abandonable mid-flight (read-only, durable
|
|
46
|
+
* mission state, no approval gate). ralplan/ultragoal deliberately stay
|
|
47
|
+
* blocked in live phases so mid-consensus or mid-execution chaining still
|
|
48
|
+
* requires the explicit write-to-handoff step.
|
|
49
|
+
*/
|
|
50
|
+
function phasePermitsChain(skill: string, phase: string): boolean {
|
|
51
|
+
if (TERMINAL_PHASES.has(phase)) return true;
|
|
52
|
+
if (!isCanonicalGjcWorkflowSkill(skill)) return false;
|
|
53
|
+
const manifest = getSkillManifest(skill);
|
|
54
|
+
if (manifest.terminalStates.includes(phase)) return true;
|
|
55
|
+
if (skill !== "autoresearch") return false;
|
|
56
|
+
return manifest.transitions.some(transition => transition.from === phase && transition.to === "handoff");
|
|
57
|
+
}
|
|
58
|
+
|
|
34
59
|
const skillSchema = z.object({
|
|
35
60
|
name: z.string().describe("skill name as it appears in /skill:<name>"),
|
|
36
61
|
args: z.string().describe("argument string passed to the skill").optional(),
|
|
@@ -122,7 +147,12 @@ export class SkillTool implements AgentTool<typeof skillSchema, SkillToolDetails
|
|
|
122
147
|
|
|
123
148
|
const skill =
|
|
124
149
|
skills.find(s => s.name === requestedName) ??
|
|
125
|
-
(await findRuntimeSkillByName(
|
|
150
|
+
(await findRuntimeSkillByName(
|
|
151
|
+
this.#session.cwd,
|
|
152
|
+
requestedName,
|
|
153
|
+
this.#getRuntimeSkillPolicy(),
|
|
154
|
+
this.#session.home,
|
|
155
|
+
));
|
|
126
156
|
if (!skill) {
|
|
127
157
|
const available = skills.map(s => s.name).sort();
|
|
128
158
|
const hint =
|
|
@@ -137,9 +167,9 @@ export class SkillTool implements AgentTool<typeof skillSchema, SkillToolDetails
|
|
|
137
167
|
// so there is no `gjc state <skill>` command to run for them.
|
|
138
168
|
if (activeSkill && isCanonicalGjcWorkflowSkill(activeSkill)) {
|
|
139
169
|
const phase = (this.#session.getActiveSkillPhase?.() ?? "running").trim().toLowerCase();
|
|
140
|
-
if (!
|
|
170
|
+
if (!phasePermitsChain(activeSkill, phase)) {
|
|
141
171
|
throw new ToolError(
|
|
142
|
-
`skill tool: refusing to chain from "${activeSkill}" (phase=${phase}) into "${requestedName}".
|
|
172
|
+
`skill tool: refusing to chain from "${activeSkill}" (phase=${phase}) into "${requestedName}". Run gjc state ${activeSkill} handoff --to ${requestedName} --json directly, or finalize the current skill first (gjc state ${activeSkill} write --input '{"current_phase":"handoff"}' --json) before chaining.`,
|
|
143
173
|
);
|
|
144
174
|
}
|
|
145
175
|
const cwd = this.#session.cwd;
|
|
@@ -11,13 +11,7 @@ import type { Component } from "@gajae-code/tui";
|
|
|
11
11
|
import { Text } from "@gajae-code/tui";
|
|
12
12
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
13
13
|
import type { Theme } from "../modes/theme/theme";
|
|
14
|
-
import {
|
|
15
|
-
collectProviderDegradationGroups,
|
|
16
|
-
hasActiveProviderRetryInProgress,
|
|
17
|
-
providerProgressAgeLabel,
|
|
18
|
-
providerRetryPhaseLabel,
|
|
19
|
-
} from "../task/provider-retry-status";
|
|
20
|
-
import { renderSubagentLiveProgress } from "../task/render";
|
|
14
|
+
import { providerRetryPhaseLabel } from "../task/provider-retry-status";
|
|
21
15
|
import { Ellipsis, Hasher, renderStatusLine } from "../tui";
|
|
22
16
|
import {
|
|
23
17
|
formatDuration,
|
|
@@ -27,7 +21,12 @@ import {
|
|
|
27
21
|
type ToolUIStatus,
|
|
28
22
|
truncateToWidth,
|
|
29
23
|
} from "./render-utils";
|
|
30
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
type SubagentLiveProgress,
|
|
26
|
+
type SubagentSnapshot,
|
|
27
|
+
type SubagentToolDetails,
|
|
28
|
+
subagentAwaitRenderedStateSignature,
|
|
29
|
+
} from "./subagent";
|
|
31
30
|
|
|
32
31
|
export { subagentAwaitRenderedStateSignature } from "./subagent";
|
|
33
32
|
|
|
@@ -89,6 +88,30 @@ function snapshotHasActiveRetry(snapshot: SubagentSnapshot): boolean {
|
|
|
89
88
|
return hasActiveProviderRetryInProgress(snapshot.progress);
|
|
90
89
|
}
|
|
91
90
|
|
|
91
|
+
function hasActiveProviderRetryInProgress(progress: SubagentLiveProgress): boolean {
|
|
92
|
+
return progress.status === "running" && progress.retryState !== undefined;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function providerProgressAgeLabel(progress: SubagentLiveProgress["retryState"], nowMs: number): string {
|
|
96
|
+
if (progress?.lastProviderProgressAtMs === undefined) return "no provider events yet";
|
|
97
|
+
const ageSeconds = Math.max(0, Math.floor((nowMs - progress.lastProviderProgressAtMs) / 1000));
|
|
98
|
+
return `last provider progress ${ageSeconds}s ago`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function collectProviderDegradationGroups(
|
|
102
|
+
progress: readonly SubagentLiveProgress[],
|
|
103
|
+
): Array<{ provider: string; count: number }> {
|
|
104
|
+
const counts = new Map<string, number>();
|
|
105
|
+
for (const item of progress) {
|
|
106
|
+
if (item.status !== "running" || !item.retryState) continue;
|
|
107
|
+
const provider = item.retryState.provider ?? "provider";
|
|
108
|
+
counts.set(provider, (counts.get(provider) ?? 0) + 1);
|
|
109
|
+
}
|
|
110
|
+
return Array.from(counts, ([provider, count]) => ({ provider, count }))
|
|
111
|
+
.filter(group => group.count > 1)
|
|
112
|
+
.sort((a, b) => b.count - a.count || a.provider.localeCompare(b.provider));
|
|
113
|
+
}
|
|
114
|
+
|
|
92
115
|
function boundSubagentBodyLines(lines: string[], width: number): string[] {
|
|
93
116
|
return lines.map(line => (line.length > 0 ? truncateToWidth(replaceTabs(line), width, Ellipsis.Omit) : ""));
|
|
94
117
|
}
|
|
@@ -166,6 +189,72 @@ function renderSubagentStatusLine(snapshot: SubagentSnapshot, theme: Theme, spin
|
|
|
166
189
|
return `${icon} ${id} ${status} ${duration}`;
|
|
167
190
|
}
|
|
168
191
|
|
|
192
|
+
function renderSubagentLiveProgress(
|
|
193
|
+
progress: SubagentLiveProgress,
|
|
194
|
+
expanded: boolean,
|
|
195
|
+
theme: Theme,
|
|
196
|
+
spinnerFrame?: number,
|
|
197
|
+
staticTime = false,
|
|
198
|
+
): string[] {
|
|
199
|
+
const lines: string[] = [];
|
|
200
|
+
const prefix = theme.fg("dim", theme.tree.last);
|
|
201
|
+
const iconColor = progress.status === "failed" || progress.status === "aborted" ? "error" : "accent";
|
|
202
|
+
const icon = formatStatusIcon(
|
|
203
|
+
progress.status === "completed" ? "success" : progress.status === "failed" ? "error" : "info",
|
|
204
|
+
theme,
|
|
205
|
+
progress.status === "running" ? spinnerFrame : undefined,
|
|
206
|
+
);
|
|
207
|
+
let statusLine = `${prefix} ${theme.fg(iconColor, icon)} ${theme.fg("accent", progress.id)}`;
|
|
208
|
+
if (progress.fastMode && theme.icon.fast) statusLine += ` ${theme.icon.fast}`;
|
|
209
|
+
if (progress.retryState && progress.status === "running") {
|
|
210
|
+
statusLine += ` ${theme.fg("warning", "provider degraded")}`;
|
|
211
|
+
}
|
|
212
|
+
lines.push(statusLine);
|
|
213
|
+
|
|
214
|
+
const continuePrefix = " ";
|
|
215
|
+
if (progress.status === "running") {
|
|
216
|
+
const tool = progress.currentTool ?? progress.recentTool;
|
|
217
|
+
if (tool) lines.push(`${continuePrefix}${theme.tree.hook} ${theme.fg("muted", tool)}`);
|
|
218
|
+
}
|
|
219
|
+
if (progress.recentOutputSummary) {
|
|
220
|
+
const count = progress.recentOutputSummary.lineCount;
|
|
221
|
+
lines.push(
|
|
222
|
+
`${continuePrefix}${theme.tree.hook} ${theme.fg("dim", `recent output available (${count} ${count === 1 ? "line" : "lines"})`)}`,
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
if (progress.retryState && progress.status === "running") {
|
|
226
|
+
const retry = progress.retryState;
|
|
227
|
+
const attemptLabel = retry.unbounded
|
|
228
|
+
? `attempt ${retry.attempt}, unbounded`
|
|
229
|
+
: `attempt ${retry.attempt} of ${retry.maxAttempts}, bounded`;
|
|
230
|
+
const progressAge = staticTime ? "" : ` · ${providerProgressAgeLabel(retry, Date.now())}`;
|
|
231
|
+
let waitLabel = "";
|
|
232
|
+
if (!staticTime) {
|
|
233
|
+
const remainingMs = Math.max(0, retry.startedAtMs + retry.delayMs - Date.now());
|
|
234
|
+
waitLabel = remainingMs > 0 ? ` in ${formatDuration(remainingMs)}` : " now";
|
|
235
|
+
}
|
|
236
|
+
lines.push(
|
|
237
|
+
`${continuePrefix}${theme.tree.hook} ${theme.fg("warning", `${providerRetryPhaseLabel(retry.kind)} · retrying ${attemptLabel}${waitLabel}${progressAge}`)}`,
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
if (progress.retryFailure && progress.status !== "running") {
|
|
241
|
+
const attempts = progress.retryFailure.attempt;
|
|
242
|
+
lines.push(
|
|
243
|
+
`${continuePrefix}${theme.tree.hook} ${theme.fg("error", `auto-retry gave up after ${attempts} attempt${attempts === 1 ? "" : "s"}`)}`,
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
if (
|
|
247
|
+
expanded &&
|
|
248
|
+
progress.status === "running" &&
|
|
249
|
+
!progress.currentTool &&
|
|
250
|
+
!progress.recentTool &&
|
|
251
|
+
!progress.recentOutputSummary
|
|
252
|
+
) {
|
|
253
|
+
lines.push(`${continuePrefix}${theme.fg("dim", "running, no approved activity summary yet")}`);
|
|
254
|
+
}
|
|
255
|
+
return lines;
|
|
256
|
+
}
|
|
257
|
+
|
|
169
258
|
// Heavy per-subagent body. The cache path uses staticTime=true; the bounded dynamic
|
|
170
259
|
// path opts into wall-clock displays when an active retry exists in the nested tree.
|
|
171
260
|
function renderSubagentSnapshotBody(
|
|
@@ -198,6 +287,20 @@ function renderSubagentSnapshotBody(
|
|
|
198
287
|
if (snapshot.setupFailureSummary) {
|
|
199
288
|
lines.push(` ${theme.fg("error", `Setup failure: ${snapshot.setupFailureSummary}`)}`);
|
|
200
289
|
}
|
|
290
|
+
if (snapshot.localErrorSummary) {
|
|
291
|
+
const local = snapshot.localErrorSummary;
|
|
292
|
+
lines.push(` ${theme.fg("error", `Local failure (${local.kind}): ${local.summary}`)}`);
|
|
293
|
+
// Kind-conditional guidance: an overflow is a staging limit that
|
|
294
|
+
// reproduces on re-issue; a snapshot failure is a serialization
|
|
295
|
+
// defect. Rendering the wrong one mislabels the failure mode.
|
|
296
|
+
const guidance =
|
|
297
|
+
local.kind === "local_buffer_overflow"
|
|
298
|
+
? "Local gjc staging-buffer limit, not a provider or context-window failure; re-issuing reproduces it."
|
|
299
|
+
: local.kind === "local_snapshot_failure"
|
|
300
|
+
? "Local gjc event-serialization defect, not a provider failure; safe to retry."
|
|
301
|
+
: undefined;
|
|
302
|
+
if (guidance) lines.push(` ${theme.fg("dim", guidance)}`);
|
|
303
|
+
}
|
|
201
304
|
if (snapshot.assignment) {
|
|
202
305
|
lines.push(` ${theme.fg("dim", "Assignment:")}`);
|
|
203
306
|
for (const al of snapshot.assignment.split("\n")) lines.push(` ${theme.fg("toolOutput", replaceTabs(al))}`);
|
|
@@ -220,7 +323,7 @@ function renderSubagentSnapshotBody(
|
|
|
220
323
|
lines.push(` ${pl}`);
|
|
221
324
|
}
|
|
222
325
|
} else if (snapshot.liveProgressAvailable && (snapshot.status === "running" || snapshot.status === "queued")) {
|
|
223
|
-
lines.push(` ${theme.fg("dim", "running, no activity yet")}`);
|
|
326
|
+
lines.push(` ${theme.fg("dim", "running, no approved activity summary yet")}`);
|
|
224
327
|
}
|
|
225
328
|
|
|
226
329
|
const preview = snapshot.errorText?.trim() || snapshot.resultText?.trim();
|
|
@@ -265,6 +368,7 @@ export const subagentToolRenderer = {
|
|
|
265
368
|
}
|
|
266
369
|
|
|
267
370
|
const runningCount = subagents.filter(s => s.status === "running").length;
|
|
371
|
+
const failedCount = subagents.filter(s => s.status === "failed" || s.status === "not_found").length;
|
|
268
372
|
const interrupted = result.details?.interrupted === true;
|
|
269
373
|
|
|
270
374
|
// Each snapshot's rendered-state signature is constant for this component
|
|
@@ -280,14 +384,16 @@ export const subagentToolRenderer = {
|
|
|
280
384
|
// it is never gated by the heavy body cache.
|
|
281
385
|
const header = renderStatusLine(
|
|
282
386
|
{
|
|
283
|
-
icon: interrupted ? "warning" : runningCount > 0 ? "info" : "success",
|
|
387
|
+
icon: interrupted ? "warning" : runningCount > 0 ? "info" : failedCount > 0 ? "error" : "success",
|
|
284
388
|
spinnerFrame: !interrupted && runningCount > 0 ? options.spinnerFrame : undefined,
|
|
285
|
-
title: interrupted ? "Subagent await interrupted" : "Subagent",
|
|
389
|
+
title: interrupted ? "Subagent await interrupted" : failedCount > 0 ? "Subagent failed" : "Subagent",
|
|
286
390
|
description: interrupted
|
|
287
391
|
? "child subagents continue"
|
|
288
392
|
: runningCount > 0
|
|
289
393
|
? `awaiting ${runningCount} of ${subagents.length}`
|
|
290
|
-
:
|
|
394
|
+
: failedCount > 0
|
|
395
|
+
? `${failedCount} ${failedCount === 1 ? "subagent" : "subagents"} failed`
|
|
396
|
+
: `${subagents.length} ${subagents.length === 1 ? "subagent" : "subagents"}`,
|
|
291
397
|
},
|
|
292
398
|
theme,
|
|
293
399
|
);
|
|
@@ -320,7 +426,7 @@ export const subagentToolRenderer = {
|
|
|
320
426
|
|
|
321
427
|
snapshotSignatures ??= subagents.map(
|
|
322
428
|
snapshot =>
|
|
323
|
-
`${subagentAwaitRenderedStateSignature([snapshot], result.details)}:${snapshot.setupFailureSummary ?? ""}`,
|
|
429
|
+
`${subagentAwaitRenderedStateSignature([snapshot], result.details)}:${snapshot.setupFailureSummary ?? ""}:${snapshot.localErrorSummary?.summary ?? ""}`,
|
|
324
430
|
);
|
|
325
431
|
subagents.forEach((snapshot, index) => {
|
|
326
432
|
// Fresh per-subagent status line (cheap), then a cached or dynamic body.
|