@gajae-code/coding-agent 0.5.1 → 0.5.2

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.
Files changed (98) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +1 -1
  3. package/dist/types/cli/setup-cli.d.ts +8 -1
  4. package/dist/types/commands/setup.d.ts +7 -0
  5. package/dist/types/config/file-lock.d.ts +24 -2
  6. package/dist/types/config/model-registry.d.ts +4 -0
  7. package/dist/types/config/models-config-schema.d.ts +5 -0
  8. package/dist/types/config/settings-schema.d.ts +62 -0
  9. package/dist/types/gjc-runtime/state-writer.d.ts +64 -2
  10. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +10 -0
  11. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +29 -0
  12. package/dist/types/modes/components/provider-onboarding-selector.d.ts +1 -1
  13. package/dist/types/modes/interactive-mode.d.ts +1 -1
  14. package/dist/types/modes/rpc/rpc-mode.d.ts +56 -1
  15. package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +10 -0
  16. package/dist/types/modes/theme/defaults/index.d.ts +302 -0
  17. package/dist/types/modes/theme/theme.d.ts +1 -0
  18. package/dist/types/modes/types.d.ts +1 -1
  19. package/dist/types/session/history-storage.d.ts +2 -2
  20. package/dist/types/session/session-manager.d.ts +10 -1
  21. package/dist/types/setup/credential-import.d.ts +79 -0
  22. package/dist/types/task/executor.d.ts +1 -0
  23. package/dist/types/task/render.d.ts +1 -1
  24. package/dist/types/tools/subagent-render.d.ts +7 -1
  25. package/dist/types/tools/subagent.d.ts +21 -0
  26. package/dist/types/tools/ultragoal-ask-guard.d.ts +5 -0
  27. package/dist/types/web/search/index.d.ts +4 -4
  28. package/dist/types/web/search/provider.d.ts +16 -20
  29. package/dist/types/web/search/providers/base.d.ts +2 -1
  30. package/dist/types/web/search/providers/openai-compatible.d.ts +9 -0
  31. package/dist/types/web/search/types.d.ts +14 -2
  32. package/package.json +7 -7
  33. package/scripts/build-binary.ts +7 -0
  34. package/src/cli/args.ts +2 -0
  35. package/src/cli/fast-help.ts +2 -0
  36. package/src/cli/setup-cli.ts +138 -3
  37. package/src/commands/setup.ts +5 -1
  38. package/src/commands/ultragoal.ts +3 -1
  39. package/src/config/file-lock-gc.ts +14 -2
  40. package/src/config/file-lock.ts +54 -12
  41. package/src/config/model-profile-activation.ts +15 -3
  42. package/src/config/model-profiles.ts +15 -15
  43. package/src/config/model-registry.ts +21 -1
  44. package/src/config/models-config-schema.ts +1 -0
  45. package/src/config/settings-schema.ts +62 -0
  46. package/src/defaults/gjc/skills/ultragoal/SKILL.md +30 -8
  47. package/src/gjc-runtime/deep-interview-recorder.ts +40 -0
  48. package/src/gjc-runtime/launch-tmux.ts +3 -4
  49. package/src/gjc-runtime/ralplan-runtime.ts +174 -12
  50. package/src/gjc-runtime/state-runtime.ts +2 -1
  51. package/src/gjc-runtime/state-writer.ts +254 -7
  52. package/src/gjc-runtime/tmux-gc.ts +2 -1
  53. package/src/gjc-runtime/ultragoal-guard.ts +155 -0
  54. package/src/gjc-runtime/ultragoal-runtime.ts +1227 -31
  55. package/src/gjc-runtime/workflow-manifest.generated.json +44 -0
  56. package/src/gjc-runtime/workflow-manifest.ts +12 -0
  57. package/src/harness-control-plane/owner.ts +3 -2
  58. package/src/harness-control-plane/rpc-adapter.ts +1 -1
  59. package/src/hooks/skill-state.ts +121 -2
  60. package/src/internal-urls/docs-index.generated.ts +13 -9
  61. package/src/lsp/defaults.json +1 -0
  62. package/src/main.ts +14 -4
  63. package/src/modes/acp/acp-agent.ts +4 -2
  64. package/src/modes/bridge/bridge-mode.ts +2 -1
  65. package/src/modes/components/history-search.ts +5 -2
  66. package/src/modes/components/model-selector.ts +26 -0
  67. package/src/modes/components/provider-onboarding-selector.ts +6 -1
  68. package/src/modes/controllers/selector-controller.ts +80 -1
  69. package/src/modes/interactive-mode.ts +11 -1
  70. package/src/modes/rpc/rpc-mode.ts +132 -18
  71. package/src/modes/shared/agent-wire/command-dispatch.ts +5 -2
  72. package/src/modes/shared/agent-wire/host-tool-bridge.ts +3 -0
  73. package/src/modes/shared/agent-wire/unattended-session.ts +16 -1
  74. package/src/modes/theme/defaults/claude-code.json +100 -0
  75. package/src/modes/theme/defaults/codex.json +100 -0
  76. package/src/modes/theme/defaults/index.ts +6 -0
  77. package/src/modes/theme/defaults/opencode.json +102 -0
  78. package/src/modes/theme/theme.ts +2 -2
  79. package/src/modes/types.ts +1 -1
  80. package/src/prompts/agents/executor.md +5 -2
  81. package/src/sdk.ts +12 -1
  82. package/src/session/agent-session.ts +22 -11
  83. package/src/session/history-storage.ts +32 -11
  84. package/src/session/session-manager.ts +70 -18
  85. package/src/setup/credential-import.ts +429 -0
  86. package/src/skill-state/deep-interview-mutation-guard.ts +2 -1
  87. package/src/task/executor.ts +7 -1
  88. package/src/task/render.ts +18 -7
  89. package/src/tools/ask.ts +4 -2
  90. package/src/tools/cron.ts +1 -1
  91. package/src/tools/subagent-render.ts +119 -29
  92. package/src/tools/subagent.ts +147 -7
  93. package/src/tools/ultragoal-ask-guard.ts +39 -0
  94. package/src/web/search/index.ts +25 -25
  95. package/src/web/search/provider.ts +178 -87
  96. package/src/web/search/providers/base.ts +2 -1
  97. package/src/web/search/providers/openai-compatible.ts +151 -0
  98. package/src/web/search/types.ts +47 -22
@@ -0,0 +1,100 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/can1357/gajae-code/main/packages/coding-agent/theme-schema.json",
3
+ "name": "claude-code",
4
+ "vars": {
5
+ "bg": "#1a1a1a",
6
+ "surface": "#373737",
7
+ "surfaceSubtle": "#2a2a2a",
8
+ "separator": "#505050",
9
+ "fg": "#ffffff",
10
+ "muted": "#888888",
11
+ "dim": "#666666",
12
+ "brandTerracotta": "#d77757",
13
+ "shimmerTerracotta": "#eb9f7f",
14
+ "toolPink": "#fd5db1",
15
+ "lavender": "#b1b9f9",
16
+ "autoAcceptPurple": "#af87ff",
17
+ "successGreen": "#4eba65",
18
+ "warningAmber": "#ffc107",
19
+ "errorSoftRed": "#ff6b80",
20
+ "diffAddedBg": "#225c2b",
21
+ "diffRemovedBg": "#7a2936",
22
+ "diffRemovedFg": "#ff8fa0"
23
+ },
24
+ "colors": {
25
+ "accent": "brandTerracotta",
26
+ "border": "separator",
27
+ "borderAccent": "toolPink",
28
+ "borderMuted": "dim",
29
+ "success": "successGreen",
30
+ "error": "errorSoftRed",
31
+ "warning": "warningAmber",
32
+ "muted": "muted",
33
+ "dim": "dim",
34
+ "text": "fg",
35
+ "thinkingText": "muted",
36
+ "selectedBg": "surface",
37
+ "userMessageBg": "surface",
38
+ "userMessageText": "fg",
39
+ "customMessageBg": "surfaceSubtle",
40
+ "customMessageText": "fg",
41
+ "customMessageLabel": "brandTerracotta",
42
+ "toolPendingBg": "surfaceSubtle",
43
+ "toolSuccessBg": "diffAddedBg",
44
+ "toolErrorBg": "diffRemovedBg",
45
+ "toolTitle": "fg",
46
+ "toolOutput": "muted",
47
+ "mdHeading": "brandTerracotta",
48
+ "mdLink": "lavender",
49
+ "mdLinkUrl": "muted",
50
+ "mdCode": "shimmerTerracotta",
51
+ "mdCodeBlock": "fg",
52
+ "mdCodeBlockBorder": "toolPink",
53
+ "mdQuote": "muted",
54
+ "mdQuoteBorder": "separator",
55
+ "mdHr": "dim",
56
+ "mdListBullet": "brandTerracotta",
57
+ "toolDiffAdded": "successGreen",
58
+ "toolDiffRemoved": "diffRemovedFg",
59
+ "toolDiffContext": "muted",
60
+ "syntaxComment": "muted",
61
+ "syntaxKeyword": "lavender",
62
+ "syntaxFunction": "shimmerTerracotta",
63
+ "syntaxVariable": "fg",
64
+ "syntaxString": "successGreen",
65
+ "syntaxNumber": "warningAmber",
66
+ "syntaxType": "autoAcceptPurple",
67
+ "syntaxOperator": "toolPink",
68
+ "syntaxPunctuation": "muted",
69
+ "thinkingOff": "dim",
70
+ "thinkingMinimal": "muted",
71
+ "thinkingLow": "shimmerTerracotta",
72
+ "thinkingMedium": "brandTerracotta",
73
+ "thinkingHigh": "toolPink",
74
+ "thinkingXhigh": "errorSoftRed",
75
+ "bashMode": "toolPink",
76
+ "pythonMode": "lavender",
77
+ "statusLineBg": "bg",
78
+ "statusLineSep": "separator",
79
+ "statusLineModel": "brandTerracotta",
80
+ "statusLinePath": "lavender",
81
+ "statusLineGitClean": "successGreen",
82
+ "statusLineGitDirty": "warningAmber",
83
+ "statusLineContext": "lavender",
84
+ "statusLineSpend": "warningAmber",
85
+ "statusLineStaged": "successGreen",
86
+ "statusLineDirty": "warningAmber",
87
+ "statusLineUntracked": "errorSoftRed",
88
+ "statusLineOutput": "fg",
89
+ "statusLineCost": "shimmerTerracotta",
90
+ "statusLineSubagents": "autoAcceptPurple"
91
+ },
92
+ "export": {
93
+ "pageBg": "#1a1a1a",
94
+ "cardBg": "#2a2a2a",
95
+ "infoBg": "#373737"
96
+ },
97
+ "symbols": {
98
+ "preset": "unicode"
99
+ }
100
+ }
@@ -0,0 +1,100 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/can1357/gajae-code/main/packages/coding-agent/theme-schema.json",
3
+ "name": "codex",
4
+ "vars": {
5
+ "bg": "#0f1115",
6
+ "surface": "#151922",
7
+ "surfaceBright": "#1c2230",
8
+ "selection": "#24313a",
9
+ "fg": "#d7d7d7",
10
+ "muted": "#8a8f98",
11
+ "dim": "#5f6670",
12
+ "borderNeutral": "#343a46",
13
+ "ansiCyan": "#00bcd4",
14
+ "cyanSoft": "#58d5e8",
15
+ "brandMagenta": "#c678dd",
16
+ "magentaSoft": "#d7a4e8",
17
+ "successGreen": "#4caf50",
18
+ "errorRed": "#ef5350",
19
+ "warningOrange": "#f59e0b",
20
+ "diffRemovalRed": "#d84a4a",
21
+ "toolSuccessBg": "#14241a",
22
+ "toolErrorBg": "#2a1718"
23
+ },
24
+ "colors": {
25
+ "accent": "ansiCyan",
26
+ "border": "borderNeutral",
27
+ "borderAccent": "ansiCyan",
28
+ "borderMuted": "dim",
29
+ "success": "successGreen",
30
+ "error": "errorRed",
31
+ "warning": "warningOrange",
32
+ "muted": "muted",
33
+ "dim": "dim",
34
+ "text": "fg",
35
+ "thinkingText": "muted",
36
+ "selectedBg": "selection",
37
+ "userMessageBg": "surface",
38
+ "userMessageText": "fg",
39
+ "customMessageBg": "surface",
40
+ "customMessageText": "fg",
41
+ "customMessageLabel": "brandMagenta",
42
+ "toolPendingBg": "surface",
43
+ "toolSuccessBg": "toolSuccessBg",
44
+ "toolErrorBg": "toolErrorBg",
45
+ "toolTitle": "fg",
46
+ "toolOutput": "muted",
47
+ "mdHeading": "brandMagenta",
48
+ "mdLink": "ansiCyan",
49
+ "mdLinkUrl": "muted",
50
+ "mdCode": "cyanSoft",
51
+ "mdCodeBlock": "fg",
52
+ "mdCodeBlockBorder": "borderNeutral",
53
+ "mdQuote": "muted",
54
+ "mdQuoteBorder": "borderNeutral",
55
+ "mdHr": "dim",
56
+ "mdListBullet": "ansiCyan",
57
+ "toolDiffAdded": "successGreen",
58
+ "toolDiffRemoved": "diffRemovalRed",
59
+ "toolDiffContext": "muted",
60
+ "syntaxComment": "dim",
61
+ "syntaxKeyword": "brandMagenta",
62
+ "syntaxFunction": "cyanSoft",
63
+ "syntaxVariable": "fg",
64
+ "syntaxString": "successGreen",
65
+ "syntaxNumber": "magentaSoft",
66
+ "syntaxType": "ansiCyan",
67
+ "syntaxOperator": "muted",
68
+ "syntaxPunctuation": "dim",
69
+ "thinkingOff": "dim",
70
+ "thinkingMinimal": "muted",
71
+ "thinkingLow": "cyanSoft",
72
+ "thinkingMedium": "ansiCyan",
73
+ "thinkingHigh": "brandMagenta",
74
+ "thinkingXhigh": "errorRed",
75
+ "bashMode": "ansiCyan",
76
+ "pythonMode": "brandMagenta",
77
+ "statusLineBg": "bg",
78
+ "statusLineSep": "dim",
79
+ "statusLineModel": "brandMagenta",
80
+ "statusLinePath": "cyanSoft",
81
+ "statusLineGitClean": "successGreen",
82
+ "statusLineGitDirty": "warningOrange",
83
+ "statusLineContext": "ansiCyan",
84
+ "statusLineSpend": "cyanSoft",
85
+ "statusLineStaged": "successGreen",
86
+ "statusLineDirty": "warningOrange",
87
+ "statusLineUntracked": "diffRemovalRed",
88
+ "statusLineOutput": "fg",
89
+ "statusLineCost": "muted",
90
+ "statusLineSubagents": "brandMagenta"
91
+ },
92
+ "export": {
93
+ "pageBg": "#0f1115",
94
+ "cardBg": "#151922",
95
+ "infoBg": "#1c2230"
96
+ },
97
+ "symbols": {
98
+ "preset": "unicode"
99
+ }
100
+ }
@@ -1,7 +1,13 @@
1
1
  import blue_crab from "./blue-crab.json" with { type: "json" };
2
+ import claude_code from "./claude-code.json" with { type: "json" };
3
+ import codex from "./codex.json" with { type: "json" };
4
+ import opencode from "./opencode.json" with { type: "json" };
2
5
  import red_claw from "./red-claw.json" with { type: "json" };
3
6
 
4
7
  export const defaultThemes = {
5
8
  "blue-crab": blue_crab,
9
+ "claude-code": claude_code,
10
+ codex,
11
+ opencode,
6
12
  "red-claw": red_claw,
7
13
  };
@@ -0,0 +1,102 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/can1357/gajae-code/main/packages/coding-agent/theme-schema.json",
3
+ "name": "opencode",
4
+ "vars": {
5
+ "background": "#212121",
6
+ "currentLine": "#252525",
7
+ "selection": "#303030",
8
+ "backgroundDarker": "#121212",
9
+ "foreground": "#e0e0e0",
10
+ "comment": "#6a6a6a",
11
+ "primary": "#fab283",
12
+ "secondary": "#5c9cf5",
13
+ "accentPurple": "#9d7cd8",
14
+ "errorRed": "#e06c75",
15
+ "warningOrange": "#f5a742",
16
+ "successGreen": "#7fd88f",
17
+ "infoCyan": "#56b6c2",
18
+ "emphasizedYellow": "#e5c07b",
19
+ "border": "#4b4c5c",
20
+ "diffAdded": "#478247",
21
+ "diffRemoved": "#7c4444",
22
+ "diffContext": "#a0a0a0",
23
+ "addedBg": "#303a30",
24
+ "removedBg": "#3a3030"
25
+ },
26
+ "colors": {
27
+ "accent": "primary",
28
+ "border": "border",
29
+ "borderAccent": "accentPurple",
30
+ "borderMuted": "comment",
31
+ "success": "successGreen",
32
+ "error": "errorRed",
33
+ "warning": "warningOrange",
34
+ "muted": "comment",
35
+ "dim": "comment",
36
+ "text": "foreground",
37
+ "thinkingText": "diffContext",
38
+ "selectedBg": "selection",
39
+ "userMessageBg": "currentLine",
40
+ "userMessageText": "foreground",
41
+ "customMessageBg": "currentLine",
42
+ "customMessageText": "foreground",
43
+ "customMessageLabel": "primary",
44
+ "toolPendingBg": "currentLine",
45
+ "toolSuccessBg": "addedBg",
46
+ "toolErrorBg": "removedBg",
47
+ "toolTitle": "foreground",
48
+ "toolOutput": "comment",
49
+ "mdHeading": "secondary",
50
+ "mdLink": "primary",
51
+ "mdLinkUrl": "infoCyan",
52
+ "mdCode": "successGreen",
53
+ "mdCodeBlock": "foreground",
54
+ "mdCodeBlockBorder": "border",
55
+ "mdQuote": "emphasizedYellow",
56
+ "mdQuoteBorder": "emphasizedYellow",
57
+ "mdHr": "comment",
58
+ "mdListBullet": "primary",
59
+ "toolDiffAdded": "diffAdded",
60
+ "toolDiffRemoved": "diffRemoved",
61
+ "toolDiffContext": "diffContext",
62
+ "syntaxComment": "comment",
63
+ "syntaxKeyword": "secondary",
64
+ "syntaxFunction": "primary",
65
+ "syntaxVariable": "errorRed",
66
+ "syntaxString": "successGreen",
67
+ "syntaxNumber": "accentPurple",
68
+ "syntaxType": "emphasizedYellow",
69
+ "syntaxOperator": "infoCyan",
70
+ "syntaxPunctuation": "foreground",
71
+ "thinkingOff": "comment",
72
+ "thinkingMinimal": "diffContext",
73
+ "thinkingLow": "infoCyan",
74
+ "thinkingMedium": "secondary",
75
+ "thinkingHigh": "accentPurple",
76
+ "thinkingXhigh": "errorRed",
77
+ "bashMode": "infoCyan",
78
+ "pythonMode": "accentPurple",
79
+ "statusLineBg": "backgroundDarker",
80
+ "statusLineSep": "border",
81
+ "statusLineModel": "primary",
82
+ "statusLinePath": "secondary",
83
+ "statusLineGitClean": "successGreen",
84
+ "statusLineGitDirty": "warningOrange",
85
+ "statusLineContext": "infoCyan",
86
+ "statusLineSpend": "emphasizedYellow",
87
+ "statusLineStaged": "diffAdded",
88
+ "statusLineDirty": "warningOrange",
89
+ "statusLineUntracked": "errorRed",
90
+ "statusLineOutput": "foreground",
91
+ "statusLineCost": "primary",
92
+ "statusLineSubagents": "accentPurple"
93
+ },
94
+ "export": {
95
+ "pageBg": "#121212",
96
+ "cardBg": "#212121",
97
+ "infoBg": "#252525"
98
+ },
99
+ "symbols": {
100
+ "preset": "unicode"
101
+ }
102
+ }
@@ -810,7 +810,7 @@ const colorValueSchema = z.union([
810
810
 
811
811
  type ColorValue = z.infer<typeof colorValueSchema>;
812
812
 
813
- const THEME_COLOR_KEYS = [
813
+ export const THEME_COLOR_KEYS = [
814
814
  "accent",
815
815
  "border",
816
816
  "borderAccent",
@@ -1648,7 +1648,7 @@ async function loadThemeJson(name: string): Promise<ThemeJson> {
1648
1648
  errorMessage += `\nMissing required color tokens:\n`;
1649
1649
  errorMessage += missingColors.map(c => ` - ${c}`).join("\n");
1650
1650
  errorMessage += `\n\nPlease add these colors to your theme's "colors" object.`;
1651
- errorMessage += `\nSee the built-in themes (red-claw.json, blue-crab.json) for reference values.`;
1651
+ errorMessage += `\nSee the built-in themes under src/modes/theme/defaults/ for reference values.`;
1652
1652
  }
1653
1653
  if (otherErrors.length > 0) {
1654
1654
  errorMessage += `\n\nOther errors:\n${otherErrors.join("\n")}`;
@@ -109,7 +109,7 @@ export interface InteractiveModeContext {
109
109
  retryLoader: Loader | undefined;
110
110
  autoCompactionEscapeHandler?: () => void;
111
111
  retryEscapeHandler?: () => void;
112
- retryCountdownTimer?: ReturnType<typeof setInterval>;
112
+ retryCountdownTimer?: NodeJS.Timeout;
113
113
  unsubscribe?: () => void;
114
114
  onInputCallback?: (input: SubmittedUserInput) => void;
115
115
  optimisticUserMessageSignature: string | undefined;
@@ -36,10 +36,13 @@ This mode activates only when the assignment explicitly labels Executor as Ultra
36
36
 
37
37
  When active:
38
38
  - Start from the approved plan/spec/acceptance criteria, then user-facing contracts, then implementation code only as supporting evidence. Treat plan/code mismatches as blockers.
39
- - Exercise the real user-facing invocation rather than inspecting internals alone: GUI/web uses browser automation plus screenshot or image verdict; CLI uses logs or terminal transcripts; API/package uses external consumer or black-box tests through the public interface; algorithm/math uses boundary, property, adversarial, and failure-mode cases.
39
+ - Exercise the real user-facing invocation rather than inspecting internals alone. Live artifacts must be runtime-valid: GUI/web needs a real automation transcript plus non-uniform screenshot; CLI needs executed argv-only replay; native/desktop/TUI needs a real screenshot, PTY capture with control codes, or app-automation transcript. `inlineEvidence` is supplemental only and is never sole proof for live surfaces.
40
+ - For CLI evidence, emit argv-only replay JSON with `schemaVersion: 1`, `kind: "cli-replay"`, `replaySafe: true`, and `command` as a string array. Use only allowlisted deterministic executables/arguments, or mark unsafe/non-deterministic commands with audited `replayExempt` metadata plus a valid structural fallback artifact.
41
+ - Native/TUI evidence must be structural, not prose-only: screenshot, app transcript, or PTY artifact with terminal control codes.
42
+ - Do not call the `ask` tool while an Ultragoal run is active; record unresolved decisions with `gjc ultragoal record-review-blockers`.
40
43
  - Try to break the work with adversarial cases, not just happy-path confirmations.
41
44
  - Report the QA matrix with the final field names `executorQa.contractCoverage`, `executorQa.surfaceEvidence`, `executorQa.adversarialCases`, and `executorQa.artifactRefs`.
42
- - Include artifact refs for every executed surface and adversarial case: transcript ids, log paths, screenshots, image verdicts, test outputs, or other durable evidence.
45
+ - Include artifact refs for every executed surface and adversarial case: transcript ids, log paths, screenshots, image verdicts, CLI replay records, PTY captures, test outputs, or other durable evidence.
43
46
  - Use `status: "not_applicable"` only for rows in `executorQa.contractCoverage` and `executorQa.surfaceEvidence`; each not-applicable row requires `contractRef` plus `reason`. `executorQa.adversarialCases` rows cannot be not-applicable.
44
47
  - Report blockers for any missing plan/spec/acceptance source, contract ambiguity, plan/code mismatch, untestable surface, failed adversarial case, shallow evidence, or missing artifact ref.
45
48
  </ultragoal_red_team_mode>
package/src/sdk.ts CHANGED
@@ -115,6 +115,7 @@ import {
115
115
  FindTool,
116
116
  getSearchTools,
117
117
  HIDDEN_TOOLS,
118
+ isConfigurableSearchProviderId,
118
119
  isSearchProviderPreference,
119
120
  type LspStartupServerInfo,
120
121
  loadSshTool,
@@ -124,6 +125,7 @@ import {
124
125
  SearchTool,
125
126
  setPreferredImageProvider,
126
127
  setPreferredSearchProvider,
128
+ setSearchFallbackProviders,
127
129
  type Tool,
128
130
  type ToolSession,
129
131
  WebSearchTool,
@@ -133,6 +135,7 @@ import {
133
135
  import { ToolContextStore } from "./tools/context";
134
136
  import { getImageGenTools } from "./tools/image-gen";
135
137
  import { wrapToolWithMetaNotice } from "./tools/output-meta";
138
+ import { guardToolForUltragoalAsk } from "./tools/ultragoal-ask-guard";
136
139
  import { EventBus } from "./utils/event-bus";
137
140
  import { buildNamedToolChoice, buildNamedToolChoiceResult } from "./utils/tool-choice";
138
141
  import { buildWorkspaceTree, type WorkspaceTree } from "./workspace-tree";
@@ -865,6 +868,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
865
868
  if (typeof webSearchProvider === "string" && isSearchProviderPreference(webSearchProvider)) {
866
869
  setPreferredSearchProvider(webSearchProvider);
867
870
  }
871
+ const webSearchFallback = settings.get("web_search.fallback");
872
+ if (Array.isArray(webSearchFallback)) {
873
+ setSearchFallbackProviders(
874
+ webSearchFallback.filter(value => typeof value === "string" && isConfigurableSearchProviderId(value)),
875
+ );
876
+ }
868
877
 
869
878
  const imageProvider = settings.get("providers.image");
870
879
  if (
@@ -1806,7 +1815,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1806
1815
 
1807
1816
  const initialTools = initialToolNames
1808
1817
  .map(name => toolRegistry.get(name))
1809
- .filter((tool): tool is AgentTool => tool !== undefined);
1818
+ .filter((tool): tool is AgentTool => tool !== undefined)
1819
+ // AgentSession tool wrapping is not installed until after Agent construction.
1820
+ .map(tool => guardToolForUltragoalAsk(tool, () => sessionManager.getCwd()));
1810
1821
 
1811
1822
  const openaiWebsocketSetting = settings.get("providers.openaiWebsockets") ?? "off";
1812
1823
  const preferOpenAICodexWebsockets =
@@ -240,6 +240,7 @@ import { normalizeLocalScheme, resolveToCwd } from "../tools/path-utils";
240
240
  import { getLatestTodoPhasesFromEntries, type TodoItem, type TodoPhase } from "../tools/todo-write";
241
241
  import { ToolAbortError, ToolError } from "../tools/tool-errors";
242
242
  import { clampTimeout } from "../tools/tool-timeouts";
243
+ import { guardToolForUltragoalAsk } from "../tools/ultragoal-ask-guard";
243
244
  import { parseCommandArgs } from "../utils/command-args";
244
245
  import { type EditMode, resolveEditMode } from "../utils/edit-mode";
245
246
  import { resolveFileDisplayMode } from "../utils/file-display-mode";
@@ -1186,6 +1187,7 @@ export class AgentSession {
1186
1187
  };
1187
1188
  this.agent.setProviderResponseInterceptor(this.#onResponse);
1188
1189
  this.agent.setRawSseEventInterceptor(this.#onSseEvent);
1190
+ this.#setGuardedAgentTools(this.agent.state.tools);
1189
1191
  this.yieldQueue = new YieldQueue({
1190
1192
  isStreaming: () => this.isStreaming,
1191
1193
  injectStreaming: message => this.agent.followUp(message),
@@ -3690,6 +3692,16 @@ export class AgentSession {
3690
3692
  }) as T;
3691
3693
  }
3692
3694
 
3695
+ #prepareToolForExecution<T extends AgentTool>(tool: T): T {
3696
+ return this.#wrapToolForDeepInterviewMutationGuard(
3697
+ this.#wrapToolForAcpPermission(guardToolForUltragoalAsk(tool, () => this.sessionManager.getCwd())),
3698
+ );
3699
+ }
3700
+
3701
+ #setGuardedAgentTools(tools: AgentTool[]): void {
3702
+ this.agent.setTools(tools.map(tool => this.#prepareToolForExecution(tool)));
3703
+ }
3704
+
3693
3705
  async #applyActiveToolsByName(
3694
3706
  toolNames: string[],
3695
3707
  options?: { persistMCPSelection?: boolean; previousSelectedMCPToolNames?: string[] },
@@ -3701,7 +3713,7 @@ export class AgentSession {
3701
3713
  for (const name of toolNames) {
3702
3714
  const tool = this.#toolRegistry.get(name);
3703
3715
  if (tool) {
3704
- tools.push(this.#wrapToolForDeepInterviewMutationGuard(this.#wrapToolForAcpPermission(tool)));
3716
+ tools.push(tool);
3705
3717
  validToolNames.push(name);
3706
3718
  }
3707
3719
  }
@@ -3718,7 +3730,7 @@ export class AgentSession {
3718
3730
  this.#selectedDiscoveredToolNames.delete(name);
3719
3731
  }
3720
3732
  }
3721
- this.agent.setTools(tools);
3733
+ this.#setGuardedAgentTools(tools);
3722
3734
 
3723
3735
  // Active tool set changed → discoverable tool list (which excludes already-active tools)
3724
3736
  // is now stale. Invalidate before any prompt-template hook reads the discovery list.
@@ -3976,6 +3988,9 @@ export class AgentSession {
3976
3988
  if (uniqueToolNames.size !== nextToolNames.length) {
3977
3989
  throw new Error("RPC host tool names must be unique");
3978
3990
  }
3991
+ if (uniqueToolNames.has("ask")) {
3992
+ throw new Error('RPC host tool "ask" is reserved and cannot be supplied by the host');
3993
+ }
3979
3994
 
3980
3995
  for (const name of uniqueToolNames) {
3981
3996
  if (this.#toolRegistry.has(name) && !this.#rpcHostToolNames.has(name)) {
@@ -4303,11 +4318,8 @@ export class AgentSession {
4303
4318
  this.#toolRegistry.set(finalTool.name, finalTool);
4304
4319
 
4305
4320
  if (!this.getActiveToolNames().includes(finalTool.name)) {
4306
- const activeTools = [
4307
- ...this.agent.state.tools,
4308
- this.#wrapToolForDeepInterviewMutationGuard(this.#wrapToolForAcpPermission(finalTool)),
4309
- ];
4310
- this.agent.setTools(activeTools);
4321
+ const activeTools = [...this.agent.state.tools, finalTool];
4322
+ this.#setGuardedAgentTools(activeTools);
4311
4323
  this.#invalidateDiscoveryCaches();
4312
4324
  void this.refreshBaseSystemPrompt().catch(error => {
4313
4325
  logger.warn("Failed to refresh system prompt after workflow gate ask tool registration", {
@@ -4339,9 +4351,8 @@ export class AgentSession {
4339
4351
  const activeToolNames = this.getActiveToolNames();
4340
4352
  const activeTools = activeToolNames
4341
4353
  .map(name => this.#toolRegistry.get(name))
4342
- .filter((tool): tool is AgentTool => tool !== undefined)
4343
- .map(tool => this.#wrapToolForAcpPermission(tool));
4344
- this.agent.setTools(activeTools);
4354
+ .filter((tool): tool is AgentTool => tool !== undefined);
4355
+ this.#setGuardedAgentTools(activeTools);
4345
4356
  }
4346
4357
 
4347
4358
  getCheckpointState(): CheckpointState | undefined {
@@ -9174,7 +9185,7 @@ export class AgentSession {
9174
9185
  error: String(mcpError),
9175
9186
  });
9176
9187
  this.#selectedMCPToolNames = new Set(previousSelectedMCPToolNames);
9177
- this.agent.setTools(previousTools);
9188
+ this.#setGuardedAgentTools(previousTools);
9178
9189
  this.#baseSystemPrompt = previousBaseSystemPrompt;
9179
9190
  this.agent.setSystemPrompt(previousSystemPrompt);
9180
9191
  }
@@ -67,10 +67,14 @@ export class HistoryStorage {
67
67
  // Prepared statements
68
68
  #insertRowStmt: Statement;
69
69
  #recentStmt: Statement;
70
+ #recentByCwdStmt: Statement;
70
71
  #searchStmt: Statement;
72
+ #searchByCwdStmt: Statement;
71
73
  #lastPromptStmt: Statement;
72
74
  // Cache substring-fallback prepared statements keyed by token count.
73
75
  #substringStmts = new Map<number, Statement>();
76
+ // Cache cwd-filtered substring-fallback statements keyed by token count.
77
+ #substringCwdStmts = new Map<number, Statement>();
74
78
 
75
79
  // In-memory cache of last prompt to avoid sync DB reads on add
76
80
  #lastPromptCache: string | null = null;
@@ -94,6 +98,7 @@ CREATE TABLE IF NOT EXISTS history (
94
98
  cwd TEXT
95
99
  );
96
100
  CREATE INDEX IF NOT EXISTS idx_history_created_at ON history(created_at DESC);
101
+ CREATE INDEX IF NOT EXISTS idx_history_cwd_created_at ON history(cwd, created_at DESC);
97
102
 
98
103
  CREATE VIRTUAL TABLE IF NOT EXISTS history_fts USING fts5(prompt, content='history', content_rowid='id');
99
104
 
@@ -117,9 +122,15 @@ CREATE TRIGGER IF NOT EXISTS history_ai AFTER INSERT ON history BEGIN
117
122
  this.#recentStmt = this.#db.prepare(
118
123
  "SELECT id, prompt, created_at, cwd FROM history ORDER BY created_at DESC, id DESC LIMIT ?",
119
124
  );
125
+ this.#recentByCwdStmt = this.#db.prepare(
126
+ "SELECT id, prompt, created_at, cwd FROM history WHERE cwd = ? ORDER BY created_at DESC, id DESC LIMIT ?",
127
+ );
120
128
  this.#searchStmt = this.#db.prepare(
121
129
  "SELECT h.id, h.prompt, h.created_at, h.cwd FROM history_fts f JOIN history h ON h.id = f.rowid WHERE history_fts MATCH ? ORDER BY h.created_at DESC, h.id DESC LIMIT ?",
122
130
  );
131
+ this.#searchByCwdStmt = this.#db.prepare(
132
+ "SELECT h.id, h.prompt, h.created_at, h.cwd FROM history_fts f JOIN history h ON h.id = f.rowid WHERE history_fts MATCH ? AND h.cwd = ? ORDER BY h.created_at DESC, h.id DESC LIMIT ?",
133
+ );
123
134
  this.#lastPromptStmt = this.#db.prepare("SELECT prompt FROM history ORDER BY id DESC LIMIT 1");
124
135
 
125
136
  this.#insertRowStmt = this.#db.prepare("INSERT INTO history (prompt, cwd) VALUES (?, ?)");
@@ -158,12 +169,14 @@ CREATE TRIGGER IF NOT EXISTS history_ai AFTER INSERT ON history BEGIN
158
169
  });
159
170
  }
160
171
 
161
- getRecent(limit: number): HistoryEntry[] {
172
+ getRecent(limit: number, cwd?: string): HistoryEntry[] {
162
173
  const safeLimit = this.#normalizeLimit(limit);
163
174
  if (safeLimit === 0) return [];
164
175
 
165
176
  try {
166
- const rows = this.#recentStmt.all(safeLimit) as HistoryRow[];
177
+ const rows = (
178
+ cwd === undefined ? this.#recentStmt.all(safeLimit) : this.#recentByCwdStmt.all(cwd, safeLimit)
179
+ ) as HistoryRow[];
167
180
  return rows.map(row => this.#toEntry(row));
168
181
  } catch (error) {
169
182
  logger.error("HistoryStorage getRecent failed", { error: String(error) });
@@ -171,7 +184,7 @@ CREATE TRIGGER IF NOT EXISTS history_ai AFTER INSERT ON history BEGIN
171
184
  }
172
185
  }
173
186
 
174
- search(query: string, limit: number): HistoryEntry[] {
187
+ search(query: string, limit: number, cwd?: string): HistoryEntry[] {
175
188
  const safeLimit = this.#normalizeLimit(limit);
176
189
  if (safeLimit === 0) return [];
177
190
 
@@ -184,7 +197,11 @@ CREATE TRIGGER IF NOT EXISTS history_ai AFTER INSERT ON history BEGIN
184
197
  const ftsQuery = tokens.map(tok => `"${tok.replace(/"/g, '""')}"*`).join(" ");
185
198
  let ftsRows: HistoryRow[] = [];
186
199
  try {
187
- ftsRows = this.#searchStmt.all(ftsQuery, safeLimit) as HistoryRow[];
200
+ ftsRows = (
201
+ cwd === undefined
202
+ ? this.#searchStmt.all(ftsQuery, safeLimit)
203
+ : this.#searchByCwdStmt.all(ftsQuery, cwd, safeLimit)
204
+ ) as HistoryRow[];
188
205
  } catch (error) {
189
206
  // Malformed FTS expression - fall through to substring path.
190
207
  logger.debug("HistoryStorage FTS query failed, using substring only", { error: String(error) });
@@ -199,7 +216,7 @@ CREATE TRIGGER IF NOT EXISTS history_ai AFTER INSERT ON history BEGIN
199
216
  // by safeLimit, ordered by recency - no full-table load into JS.
200
217
  let subRows: HistoryRow[] = [];
201
218
  try {
202
- subRows = this.#searchSubstring(tokens, safeLimit);
219
+ subRows = this.#searchSubstring(tokens, safeLimit, cwd);
203
220
  } catch (error) {
204
221
  logger.error("HistoryStorage substring search failed", { error: String(error) });
205
222
  }
@@ -250,6 +267,7 @@ CREATE TABLE history (
250
267
  cwd TEXT
251
268
  );
252
269
  CREATE INDEX IF NOT EXISTS idx_history_created_at ON history(created_at DESC);
270
+ CREATE INDEX IF NOT EXISTS idx_history_cwd_created_at ON history(cwd, created_at DESC);
253
271
  INSERT INTO history (id, prompt, created_at, cwd)
254
272
  SELECT id, prompt, created_at, cwd
255
273
  FROM history_legacy;
@@ -282,21 +300,24 @@ END;
282
300
  .filter(tok => tok.length > 0);
283
301
  }
284
302
 
285
- #searchSubstring(tokens: string[], limit: number): HistoryRow[] {
286
- const stmt = this.#getSubstringStmt(tokens.length);
303
+ #searchSubstring(tokens: string[], limit: number, cwd?: string): HistoryRow[] {
304
+ const stmt = this.#getSubstringStmt(tokens.length, cwd !== undefined);
287
305
  const params: unknown[] = tokens.map(tok => `%${escapeLikePattern(tok)}%`);
306
+ if (cwd !== undefined) params.push(cwd);
288
307
  params.push(limit);
289
308
  return stmt.all(...(params as [string, ...unknown[]])) as HistoryRow[];
290
309
  }
291
310
 
292
- #getSubstringStmt(tokenCount: number): Statement {
293
- let stmt = this.#substringStmts.get(tokenCount);
311
+ #getSubstringStmt(tokenCount: number, withCwd: boolean): Statement {
312
+ const cache = withCwd ? this.#substringCwdStmts : this.#substringStmts;
313
+ let stmt = cache.get(tokenCount);
294
314
  if (stmt) return stmt;
295
315
  const whereClause = Array(tokenCount).fill("prompt LIKE ? ESCAPE '\\' COLLATE NOCASE").join(" AND ");
316
+ const cwdClause = withCwd ? " AND cwd = ?" : "";
296
317
  stmt = this.#db.prepare(
297
- `SELECT id, prompt, created_at, cwd FROM history WHERE ${whereClause} ORDER BY created_at DESC, id DESC LIMIT ?`,
318
+ `SELECT id, prompt, created_at, cwd FROM history WHERE ${whereClause}${cwdClause} ORDER BY created_at DESC, id DESC LIMIT ?`,
298
319
  );
299
- this.#substringStmts.set(tokenCount, stmt);
320
+ cache.set(tokenCount, stmt);
300
321
  return stmt;
301
322
  }
302
323