longer-agent 0.1.1 → 0.1.3

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 (134) hide show
  1. package/README.md +74 -178
  2. package/README.zh-CN.md +74 -178
  3. package/agent_templates/executor/agent.yaml +1 -0
  4. package/agent_templates/executor/system_prompt.md +1 -1
  5. package/agent_templates/explorer/agent.yaml +1 -0
  6. package/agent_templates/explorer/system_prompt.md +1 -1
  7. package/agent_templates/main/agent.yaml +1 -0
  8. package/agent_templates/main/system_prompt.md +3 -2
  9. package/dist/agents/tool-loop.d.ts.map +1 -1
  10. package/dist/agents/tool-loop.js +6 -0
  11. package/dist/agents/tool-loop.js.map +1 -1
  12. package/dist/auth/openai-oauth.d.ts.map +1 -1
  13. package/dist/auth/openai-oauth.js +6 -10
  14. package/dist/auth/openai-oauth.js.map +1 -1
  15. package/dist/cli.d.ts +1 -2
  16. package/dist/cli.d.ts.map +1 -1
  17. package/dist/cli.js +81 -60
  18. package/dist/cli.js.map +1 -1
  19. package/dist/commands.d.ts +6 -1
  20. package/dist/commands.d.ts.map +1 -1
  21. package/dist/commands.js +115 -27
  22. package/dist/commands.js.map +1 -1
  23. package/dist/config.d.ts +19 -26
  24. package/dist/config.d.ts.map +1 -1
  25. package/dist/config.js +80 -120
  26. package/dist/config.js.map +1 -1
  27. package/dist/dotenv.d.ts +18 -0
  28. package/dist/dotenv.d.ts.map +1 -0
  29. package/dist/dotenv.js +91 -0
  30. package/dist/dotenv.js.map +1 -0
  31. package/dist/home-path.d.ts +3 -0
  32. package/dist/home-path.d.ts.map +1 -0
  33. package/dist/home-path.js +7 -0
  34. package/dist/home-path.js.map +1 -0
  35. package/dist/index.d.ts +6 -2
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +10 -2
  38. package/dist/index.js.map +1 -1
  39. package/dist/init-wizard.d.ts +2 -3
  40. package/dist/init-wizard.d.ts.map +1 -1
  41. package/dist/init-wizard.js +349 -145
  42. package/dist/init-wizard.js.map +1 -1
  43. package/dist/log-projection.d.ts.map +1 -1
  44. package/dist/log-projection.js +71 -12
  45. package/dist/log-projection.js.map +1 -1
  46. package/dist/managed-provider-credentials.d.ts +23 -0
  47. package/dist/managed-provider-credentials.d.ts.map +1 -0
  48. package/dist/managed-provider-credentials.js +56 -0
  49. package/dist/managed-provider-credentials.js.map +1 -0
  50. package/dist/mcp-client.d.ts.map +1 -1
  51. package/dist/mcp-client.js +2 -1
  52. package/dist/mcp-client.js.map +1 -1
  53. package/dist/mcp-config.d.ts +13 -0
  54. package/dist/mcp-config.d.ts.map +1 -0
  55. package/dist/mcp-config.js +64 -0
  56. package/dist/mcp-config.js.map +1 -0
  57. package/dist/model-discovery.d.ts +20 -0
  58. package/dist/model-discovery.d.ts.map +1 -0
  59. package/dist/model-discovery.js +47 -0
  60. package/dist/model-discovery.js.map +1 -0
  61. package/dist/model-selection.d.ts +1 -1
  62. package/dist/model-selection.d.ts.map +1 -1
  63. package/dist/model-selection.js +20 -10
  64. package/dist/model-selection.js.map +1 -1
  65. package/dist/persistence.d.ts +14 -1
  66. package/dist/persistence.d.ts.map +1 -1
  67. package/dist/persistence.js +12 -8
  68. package/dist/persistence.js.map +1 -1
  69. package/dist/provider-credential-flow.d.ts +28 -0
  70. package/dist/provider-credential-flow.d.ts.map +1 -0
  71. package/dist/provider-credential-flow.js +112 -0
  72. package/dist/provider-credential-flow.js.map +1 -0
  73. package/dist/provider-presets.d.ts +4 -0
  74. package/dist/provider-presets.d.ts.map +1 -1
  75. package/dist/provider-presets.js +26 -9
  76. package/dist/provider-presets.js.map +1 -1
  77. package/dist/providers/registry.d.ts.map +1 -1
  78. package/dist/providers/registry.js +3 -2
  79. package/dist/providers/registry.js.map +1 -1
  80. package/dist/session.d.ts +6 -17
  81. package/dist/session.d.ts.map +1 -1
  82. package/dist/session.js +82 -216
  83. package/dist/session.js.map +1 -1
  84. package/dist/settings.d.ts +8 -27
  85. package/dist/settings.d.ts.map +1 -1
  86. package/dist/settings.js +4 -108
  87. package/dist/settings.js.map +1 -1
  88. package/dist/templates/loader.d.ts.map +1 -1
  89. package/dist/templates/loader.js +2 -0
  90. package/dist/templates/loader.js.map +1 -1
  91. package/dist/tools/basic.d.ts +2 -2
  92. package/dist/tools/basic.d.ts.map +1 -1
  93. package/dist/tools/basic.js +54 -7
  94. package/dist/tools/basic.js.map +1 -1
  95. package/dist/tools/comm.d.ts.map +1 -1
  96. package/dist/tools/comm.js +8 -7
  97. package/dist/tools/comm.js.map +1 -1
  98. package/dist/tui/app.d.ts.map +1 -1
  99. package/dist/tui/app.js +176 -39
  100. package/dist/tui/app.js.map +1 -1
  101. package/dist/tui/components/command-prompt-panel.d.ts +22 -0
  102. package/dist/tui/components/command-prompt-panel.d.ts.map +1 -0
  103. package/dist/tui/components/command-prompt-panel.js +26 -0
  104. package/dist/tui/components/command-prompt-panel.js.map +1 -0
  105. package/dist/tui/components/input-panel.d.ts.map +1 -1
  106. package/dist/tui/components/input-panel.js +8 -6
  107. package/dist/tui/components/input-panel.js.map +1 -1
  108. package/dist/tui/components/logo-panel.d.ts.map +1 -1
  109. package/dist/tui/components/logo-panel.js +2 -4
  110. package/dist/tui/components/logo-panel.js.map +1 -1
  111. package/dist/tui/components/plan-panel.d.ts +3 -1
  112. package/dist/tui/components/plan-panel.d.ts.map +1 -1
  113. package/dist/tui/components/plan-panel.js +15 -2
  114. package/dist/tui/components/plan-panel.js.map +1 -1
  115. package/dist/tui/components/status-bar.d.ts.map +1 -1
  116. package/dist/tui/components/status-bar.js +17 -4
  117. package/dist/tui/components/status-bar.js.map +1 -1
  118. package/dist/tui/status-bar-model-name.d.ts +2 -0
  119. package/dist/tui/status-bar-model-name.d.ts.map +1 -0
  120. package/dist/tui/status-bar-model-name.js +81 -0
  121. package/dist/tui/status-bar-model-name.js.map +1 -0
  122. package/dist/update-check.d.ts +19 -0
  123. package/dist/update-check.d.ts.map +1 -0
  124. package/dist/update-check.js +116 -0
  125. package/dist/update-check.js.map +1 -0
  126. package/dist/version.d.ts +2 -0
  127. package/dist/version.d.ts.map +1 -0
  128. package/dist/version.js +7 -0
  129. package/dist/version.js.map +1 -0
  130. package/package.json +9 -5
  131. package/prompts/tools/plan.md +11 -240
  132. package/prompts/tools/summarize_context.md +1 -1
  133. package/prompts/tools/time.md +6 -0
  134. package/configExample.yaml +0 -83
package/dist/session.js CHANGED
@@ -16,7 +16,7 @@ import { Agent, isNoReply, NO_REPLY_MARKER } from "./agents/agent.js";
16
16
  import { createEphemeralLogState } from "./ephemeral-log.js";
17
17
  import { allocateContextId, stripContextTags, ContextTagStripBuffer } from "./context-rendering.js";
18
18
  import { generateShowContext } from "./show-context.js";
19
- import { getThinkingLevels, getModelMaxOutputTokens } from "./config.js";
19
+ import { getThinkingLevels } from "./config.js";
20
20
  import { ToolResult } from "./providers/base.js";
21
21
  import { SPAWN_AGENT_TOOL, KILL_AGENT_TOOL, CHECK_STATUS_TOOL, WAIT_TOOL, SHOW_CONTEXT_TOOL, SUMMARIZE_CONTEXT_TOOL, ASK_TOOL, PLAN_TOOL, } from "./tools/comm.js";
22
22
  import { buildBashEnv, executeTool, } from "./tools/basic.js";
@@ -30,7 +30,7 @@ import { LogIdAllocator, createSystemPrompt, createTurnStart, createUserMessage
30
30
  import { projectToApiMessages } from "./log-projection.js";
31
31
  import { archiveWindow, createGlobalTuiPreferences, createLogSessionMeta, } from "./persistence.js";
32
32
  import { resolvePersistedModelSelection, } from "./model-selection.js";
33
- import { DEFAULT_THRESHOLDS, computeHysteresisThresholds, } from "./settings.js";
33
+ import { DEFAULT_THRESHOLDS, } from "./settings.js";
34
34
  // ------------------------------------------------------------------
35
35
  // Constants
36
36
  // ------------------------------------------------------------------
@@ -43,13 +43,13 @@ const COMPACT_PROMPT_OUTPUT = `Distill this conversation into a continuation pro
43
43
 
44
44
  **Before writing the continuation prompt**, update your important log with any key discoveries, decisions, or insights from this session that aren't already recorded there. The important log survives compaction and will be visible to the new instance — this is your last chance to persist valuable knowledge.
45
45
 
46
- **What the new instance will already have:** your system prompt, the important log, AGENTS.md persistent memory, and the active plan file (if any) are automatically re-injected after compact. Do not duplicate their contents in the continuation prompt — focus on what they don't cover: current progress, session-specific context, and in-flight work state. If you've discovered stable, long-term knowledge during this session, consider persisting it to the project AGENTS.md before compaction.
46
+ **What the new instance will already have:** your system prompt, the important log, AGENTS.md persistent memory, and the active plan (if any) are automatically re-injected after compact. Do not duplicate their contents in the continuation prompt — focus on what they don't cover: current progress, session-specific context, and in-flight work state. If you've discovered stable, long-term knowledge during this session, consider persisting it to the project AGENTS.md before compaction.
47
47
 
48
48
  Your summary should capture everything that matters and nothing that doesn't. Use whatever structure best fits the actual content — there is no fixed template. But as you write, pressure-test yourself against these questions:
49
49
 
50
50
  - **What are we trying to do?** The user's intent, goals, and any constraints or preferences they've expressed — stated or implied.
51
51
  - **What do we know now that we didn't at the start?** Key discoveries, failed approaches, edge cases encountered, decisions made and *why*. (Skip anything already in your important log.)
52
- - **Where exactly are we?** What's done, what's in progress, what's next. Be specific enough that work won't be repeated or skipped. (Skip anything already in your plan file.)
52
+ - **Where exactly are we?** What's done, what's in progress, what's next. Be specific enough that work won't be repeated or skipped. (Skip anything already tracked in your plan.)
53
53
  - **What artifacts exist?** Files read, created, or modified — with enough context about each to be actionable (not just a path list).
54
54
  - **What tone/style/working relationship has been established?** If the user has shown preferences for how they like to collaborate, note them.
55
55
  - **What explicit rules has the user stated?** Direct instructions about how to work, what not to do, approval requirements, or behavioral constraints the user has explicitly communicated (e.g., "don't modify code until I approve", "always run tests before committing"). Preserve these verbatim — they are binding rules, not suggestions.
@@ -64,14 +64,14 @@ You just made a tool call and received its result above. That result is real and
64
64
 
65
65
  **Before writing the continuation prompt**, update your important log with any key discoveries, decisions, or insights from this session that aren't already recorded there. The important log survives compaction and will be visible to the new instance — this is your last chance to persist valuable knowledge.
66
66
 
67
- **What the new instance will already have:** your system prompt, the important log, AGENTS.md persistent memory, and the active plan file (if any) are automatically re-injected after compact. Do not duplicate their contents in the continuation prompt — focus on what they don't cover: current progress, session-specific context, and in-flight work state. If you've discovered stable, long-term knowledge during this session, consider persisting it to the project AGENTS.md before compaction.
67
+ **What the new instance will already have:** your system prompt, the important log, AGENTS.md persistent memory, and the active plan (if any) are automatically re-injected after compact. Do not duplicate their contents in the continuation prompt — focus on what they don't cover: current progress, session-specific context, and in-flight work state. If you've discovered stable, long-term knowledge during this session, consider persisting it to the project AGENTS.md before compaction.
68
68
 
69
69
  Write in natural prose. Use structure where it aids clarity, not for its own sake. As you write, pressure-test yourself against these questions:
70
70
 
71
71
  - **What are we trying to do?** The user's intent, goals, constraints, and preferences — stated or implied.
72
72
  - **What do we know now that we didn't at the start?** Key discoveries, failed approaches, edge cases encountered, decisions made and why. (Skip anything already in your important log.)
73
73
  - **Where exactly did we stop?** Be precise: what was the last tool call, what did it return, and what was supposed to happen next? The new instance must be able to pick up mid-step without repeating or skipping anything.
74
- - **What's done, what's in progress, what remains?** Give a clear picture of overall progress, not just the interrupted step. (Skip anything already in your plan file.)
74
+ - **What's done, what's in progress, what remains?** Give a clear picture of overall progress, not just the interrupted step. (Skip anything already tracked in your plan.)
75
75
  - **What artifacts exist?** Files read, created, or modified — with enough context about each to be actionable.
76
76
  - **What working style has the user shown?** Communication preferences, collaboration patterns, or explicit instructions about how they like to work.
77
77
  - **What explicit rules has the user stated?** Direct instructions about how to work, what not to do, approval requirements, or behavioral constraints (e.g., "don't modify code until I approve", "always run tests before committing"). Preserve these verbatim — they are binding rules, not suggestions.
@@ -211,15 +211,14 @@ export class Session {
211
211
  _thresholds = { ...DEFAULT_THRESHOLDS };
212
212
  _hintResetNone = DEFAULT_THRESHOLDS.summarize_hint_level1 / 100 - 0.20;
213
213
  _hintResetLevel1 = (DEFAULT_THRESHOLDS.summarize_hint_level1 + DEFAULT_THRESHOLDS.summarize_hint_level2) / 200;
214
- // Global max_output_tokens override from settings.json
215
- _settingsMaxOutputTokens;
214
+ // Context window multiplier (0.0–1.0). Effective context = contextLength × _contextRatio.
215
+ _contextRatio = 1.0;
216
216
  // Hint compression (two-tier state machine)
217
217
  _hintState = "none";
218
218
  // show_context: number of remaining rounds where annotations are active
219
219
  _showContextRoundsRemaining = 0;
220
220
  _showContextAnnotations = null;
221
- // Plan tracking
222
- _activePlanFile = null;
221
+ // Plan tracking (inline-only, no plan file)
223
222
  _activePlanCheckpoints = [];
224
223
  _activePlanChecked = [];
225
224
  // Skills
@@ -275,19 +274,18 @@ export class Session {
275
274
  this._progress = opts.progress;
276
275
  this._mcpManager = opts.mcpManager;
277
276
  this._promptsDirs = opts.promptsDirs;
278
- // Apply user settings (thresholds + max_output_tokens)
279
- if (opts.settings) {
280
- this._applySettings(opts.settings);
277
+ // Apply context ratio
278
+ if (opts.contextRatio !== undefined) {
279
+ this._contextRatio = Math.max(0.01, Math.min(1.0, opts.contextRatio));
281
280
  }
282
281
  // Attach store if provided (must be set before _initConversation)
283
282
  if (opts.store) {
284
283
  this._store = opts.store;
285
284
  }
286
285
  // Resolve path variables
287
- const pathOverrides = opts.config.pathOverrides;
288
- this._projectRoot = pathOverrides.projectRoot ?? process.cwd();
289
- this._sessionArtifactsOverride = pathOverrides.sessionArtifacts ?? "";
290
- this._systemData = pathOverrides.systemData ?? "";
286
+ this._projectRoot = process.cwd();
287
+ this._sessionArtifactsOverride = "";
288
+ this._systemData = "";
291
289
  this._createdAt = new Date().toISOString();
292
290
  this._initConversation();
293
291
  this._toolExecutors = this._buildToolExecutors();
@@ -318,26 +316,10 @@ export class Session {
318
316
  this._appendEntry(createSystemPrompt(this._nextLogId("system_prompt"), systemPrompt), false);
319
317
  }
320
318
  /**
321
- * Apply resolved user settings (thresholds + max_output_tokens).
319
+ * Effective context length for a given ModelConfig, scaled by context ratio.
322
320
  */
323
- _applySettings(s) {
324
- this._thresholds = { ...s.thresholds };
325
- const hysteresis = computeHysteresisThresholds(s.thresholds);
326
- this._hintResetNone = hysteresis.hintResetNone / 100;
327
- this._hintResetLevel1 = hysteresis.hintResetLevel1 / 100;
328
- this._settingsMaxOutputTokens = s.maxOutputTokens;
329
- // Apply to current primary agent's model config
330
- this._applyMaxOutputTokensOverride(this.primaryAgent.modelConfig);
331
- }
332
- /**
333
- * Effective maxTokens for a given ModelConfig, taking settings override into account.
334
- * Clamps to [4096, modelMaxOutputTokens].
335
- */
336
- _effectiveMaxTokens(mc) {
337
- if (this._settingsMaxOutputTokens === undefined)
338
- return mc.maxTokens;
339
- const modelMax = getModelMaxOutputTokens(mc.model);
340
- return Math.max(4096, Math.min(this._settingsMaxOutputTokens, modelMax ?? mc.maxTokens));
321
+ _effectiveContextLength(mc) {
322
+ return Math.round(mc.contextLength * this._contextRatio);
341
323
  }
342
324
  // ==================================================================
343
325
  // Message infrastructure
@@ -760,7 +742,6 @@ export class Session {
760
742
  this._shellCounter = 0;
761
743
  this._showContextRoundsRemaining = 0;
762
744
  this._showContextAnnotations = null;
763
- this._activePlanFile = null;
764
745
  this._activePlanCheckpoints = [];
765
746
  this._activePlanChecked = [];
766
747
  }
@@ -790,7 +771,6 @@ export class Session {
790
771
  const restoredThinkingPreference = meta.thinkingLevel ?? "default";
791
772
  const restoredCachePreference = meta.cacheHitEnabled ?? true;
792
773
  this._resetTransientState();
793
- this._applyMaxOutputTokensOverride(restoredModelConfig);
794
774
  this.primaryAgent.replaceModelConfig(restoredModelConfig);
795
775
  this._persistedModelSelection = this._buildPersistedModelSelection({
796
776
  modelConfigName: restoredSelection.selectedConfigName,
@@ -828,19 +808,9 @@ export class Session {
828
808
  // Restore ask state from log: find unclosed ask_request
829
809
  this._restoreAskStateFromLog(entries);
830
810
  // Restore active plan from meta
831
- if (meta.activePlanFile) {
832
- try {
833
- const content = readFileSync(meta.activePlanFile, "utf-8");
834
- const { checkpoints, checked } = this._parsePlanCheckpoints(content);
835
- if (checkpoints.length > 0) {
836
- this._activePlanFile = meta.activePlanFile;
837
- this._activePlanCheckpoints = checkpoints;
838
- this._activePlanChecked = checked;
839
- }
840
- }
841
- catch {
842
- // Plan file no longer exists — skip restoration
843
- }
811
+ if (meta.activePlanCheckpoints && meta.activePlanCheckpoints.length > 0) {
812
+ this._activePlanCheckpoints = meta.activePlanCheckpoints;
813
+ this._activePlanChecked = meta.activePlanChecked ?? meta.activePlanCheckpoints.map(() => false);
844
814
  }
845
815
  // Rebuild ask history from ask_resolution entries
846
816
  this._askHistory = [];
@@ -877,7 +847,8 @@ export class Session {
877
847
  thinkingLevel: this._thinkingLevel,
878
848
  cacheHitEnabled: this._cacheHitEnabled,
879
849
  summary: this._generateSummary(),
880
- activePlanFile: this._activePlanFile ?? undefined,
850
+ activePlanCheckpoints: this._activePlanCheckpoints.length > 0 ? this._activePlanCheckpoints : undefined,
851
+ activePlanChecked: this._activePlanChecked.length > 0 ? this._activePlanChecked : undefined,
881
852
  }),
882
853
  entries: this._log,
883
854
  };
@@ -969,6 +940,12 @@ export class Session {
969
940
  get skills() {
970
941
  return this._skills;
971
942
  }
943
+ get mcpManager() {
944
+ return this._mcpManager;
945
+ }
946
+ async ensureMcpReady() {
947
+ await this._ensureMcp();
948
+ }
972
949
  /** Read-only access to disabled skill names. */
973
950
  get disabledSkills() {
974
951
  return this._disabledSkills;
@@ -1145,7 +1122,6 @@ export class Session {
1145
1122
  */
1146
1123
  switchModel(modelConfigName) {
1147
1124
  const newModelConfig = this.config.getModel(modelConfigName);
1148
- this._applyMaxOutputTokensOverride(newModelConfig);
1149
1125
  this.primaryAgent.replaceModelConfig(newModelConfig);
1150
1126
  this._persistedModelSelection = this._buildPersistedModelSelection({
1151
1127
  modelConfigName,
@@ -1156,16 +1132,6 @@ export class Session {
1156
1132
  this._thinkingLevel = this._resolveThinkingLevelForModel(newModelConfig.model, this._preferredThinkingLevel);
1157
1133
  this._cacheHitEnabled = this._preferredCacheHitEnabled;
1158
1134
  }
1159
- /**
1160
- * If settings.json specifies max_output_tokens, clamp the ModelConfig.maxTokens
1161
- * to [4096, modelMaxOutputTokens]. This mutates the ModelConfig in place.
1162
- */
1163
- _applyMaxOutputTokensOverride(mc) {
1164
- if (this._settingsMaxOutputTokens === undefined)
1165
- return;
1166
- const modelMax = getModelMaxOutputTokens(mc.model) ?? mc.maxTokens;
1167
- mc.maxTokens = Math.max(4096, Math.min(this._settingsMaxOutputTokens, modelMax));
1168
- }
1169
1135
  applyGlobalPreferences(preferences) {
1170
1136
  const prefs = createGlobalTuiPreferences(preferences);
1171
1137
  this._preferredThinkingLevel = prefs.thinkingLevel;
@@ -1258,10 +1224,10 @@ export class Session {
1258
1224
  _armShowContextAnnotations() {
1259
1225
  const mc = this.primaryAgent.modelConfig;
1260
1226
  const provider = this.primaryAgent._provider;
1261
- const effectiveMax = this._effectiveMaxTokens(mc);
1227
+ const effectiveMax = mc.maxTokens;
1262
1228
  const budget = provider.budgetCalcMode === "full_context"
1263
- ? mc.contextLength
1264
- : mc.contextLength - effectiveMax;
1229
+ ? this._effectiveContextLength(mc)
1230
+ : this._effectiveContextLength(mc) - effectiveMax;
1265
1231
  const result = generateShowContext(this._log, this._lastInputTokens, budget);
1266
1232
  this._showContextRoundsRemaining = 1;
1267
1233
  this._showContextAnnotations = result.annotations;
@@ -2007,26 +1973,10 @@ export class Session {
2007
1973
  ? this._showContextAnnotations ?? undefined
2008
1974
  : undefined;
2009
1975
  let importantLog = this._readImportantLog();
2010
- // Inject active plan content alongside important log
2011
- if (this._activePlanFile) {
2012
- try {
2013
- const planContent = readFileSync(this._activePlanFile, "utf-8");
2014
- if (planContent) {
2015
- importantLog += `\n\n---\n## Active Plan\n${planContent}`;
2016
- // Detect checkpoint changes from file edits and emit update
2017
- const { checkpoints, checked } = this._parsePlanCheckpoints(planContent);
2018
- if (checkpoints.length !== this._activePlanCheckpoints.length ||
2019
- checkpoints.some((t, i) => t !== this._activePlanCheckpoints[i]) ||
2020
- checked.some((c, i) => c !== this._activePlanChecked[i])) {
2021
- this._activePlanCheckpoints = checkpoints;
2022
- this._activePlanChecked = checked;
2023
- this._emitPlanProgress("plan_update");
2024
- }
2025
- }
2026
- }
2027
- catch {
2028
- // Plan file may have been deleted externally — ignore
2029
- }
1976
+ // Inject active plan alongside important log
1977
+ if (this._activePlanCheckpoints.length > 0) {
1978
+ const lines = this._activePlanCheckpoints.map((text, i) => `- [${this._activePlanChecked[i] ? "x" : " "}] ${text}`);
1979
+ importantLog += `\n\n---\n## Active Plan\n${lines.join("\n")}`;
2030
1980
  }
2031
1981
  const agentsMd = this._readAgentsMd();
2032
1982
  return projectToApiMessages(this._log, {
@@ -2269,9 +2219,9 @@ export class Session {
2269
2219
  }
2270
2220
  const mc = this.primaryAgent.modelConfig;
2271
2221
  const provider = this.primaryAgent._provider;
2272
- const effectiveMax = this._effectiveMaxTokens(mc);
2222
+ const effectiveMax = mc.maxTokens;
2273
2223
  const budget = provider.budgetCalcMode === "full_context"
2274
- ? mc.contextLength : mc.contextLength - effectiveMax;
2224
+ ? this._effectiveContextLength(mc) : this._effectiveContextLength(mc) - effectiveMax;
2275
2225
  const result = generateShowContext(this._log, this._lastInputTokens, budget);
2276
2226
  this._showContextRoundsRemaining = 1;
2277
2227
  this._showContextAnnotations = result.annotations;
@@ -2346,149 +2296,68 @@ export class Session {
2346
2296
  // ==================================================================
2347
2297
  _execPlan(args) {
2348
2298
  const action = args["action"];
2349
- if (typeof action !== "string" || !["submit", "check", "finish"].includes(action)) {
2350
- return this._toolArgError("plan", "'action' must be one of: submit, check, finish.");
2299
+ if (typeof action !== "string" || !["submit", "check", "dismiss"].includes(action)) {
2300
+ return this._toolArgError("plan", "'action' must be one of: submit, check, dismiss.");
2351
2301
  }
2352
2302
  if (action === "submit") {
2353
- const fileArg = this._argRequiredString("plan", args, "file", { nonEmpty: true });
2354
- if (fileArg instanceof ToolResult)
2355
- return fileArg;
2356
- const fileRel = fileArg.trim();
2357
- const artifactsDir = this._resolveSessionArtifacts();
2358
- let filePath;
2359
- try {
2360
- filePath = safePath({
2361
- baseDir: artifactsDir,
2362
- requestedPath: fileRel,
2363
- cwd: artifactsDir,
2364
- mustExist: true,
2365
- expectFile: true,
2366
- accessKind: "read",
2367
- }).safePath;
2368
- }
2369
- catch (e) {
2370
- if (e instanceof SafePathError) {
2371
- if (e.code === "PATH_NOT_FOUND" || e.code === "PATH_NOT_FILE") {
2372
- const candidatePath = e.details.resolvedPath || join(artifactsDir, fileRel);
2373
- return new ToolResult({
2374
- content: `Error: plan file not found at ${candidatePath}\n` +
2375
- `The 'file' parameter is resolved relative to SESSION_ARTIFACTS (${artifactsDir}).\n` +
2376
- `Make sure you wrote the plan file to this directory using write_file(path="${join(artifactsDir, fileRel)}").`,
2377
- });
2378
- }
2379
- return new ToolResult({ content: `Error: invalid plan file path: ${e.message}` });
2380
- }
2381
- throw e;
2382
- }
2383
- let content;
2384
- try {
2385
- content = readFileSync(filePath, "utf-8");
2386
- }
2387
- catch (e) {
2388
- return new ToolResult({
2389
- content: `Error: could not read plan file: ${e instanceof Error ? e.message : String(e)}`,
2390
- });
2303
+ const raw = args["checkpoints"];
2304
+ if (!Array.isArray(raw) || raw.length === 0) {
2305
+ return this._toolArgError("plan", "'checkpoints' must be a non-empty array of strings.");
2391
2306
  }
2392
- const { checkpoints, checked } = this._parsePlanCheckpoints(content);
2307
+ const checkpoints = raw.map((c) => String(c).trim()).filter(Boolean);
2393
2308
  if (checkpoints.length === 0) {
2394
- return new ToolResult({
2395
- content: "Error: no checkpoints found in plan file. " +
2396
- "Expected a '## Checkpoints' section with items like '- [ ] Do something'.",
2397
- });
2309
+ return this._toolArgError("plan", "'checkpoints' items must not be empty.");
2398
2310
  }
2399
- this._activePlanFile = filePath;
2400
2311
  this._activePlanCheckpoints = checkpoints;
2401
- this._activePlanChecked = checked;
2312
+ this._activePlanChecked = checkpoints.map(() => false);
2402
2313
  this._emitPlanProgress("plan_submit");
2403
2314
  return new ToolResult({
2404
2315
  content: `Plan submitted with ${checkpoints.length} checkpoints.`,
2405
2316
  });
2406
2317
  }
2407
2318
  if (action === "check") {
2408
- if (!this._activePlanFile) {
2319
+ if (this._activePlanCheckpoints.length === 0) {
2409
2320
  return new ToolResult({ content: "Error: no active plan. Use action='submit' first." });
2410
2321
  }
2411
2322
  const item = args["item"];
2412
2323
  if (typeof item !== "number" || !Number.isInteger(item)) {
2413
- return this._toolArgError("plan", "'item' must be an integer index.");
2324
+ return this._toolArgError("plan", "'item' must be an integer index (1-based).");
2414
2325
  }
2415
- // Re-parse checkpoints from file to handle edits since submit
2416
- let currentContent;
2417
- try {
2418
- currentContent = readFileSync(this._activePlanFile, "utf-8");
2419
- }
2420
- catch {
2421
- return new ToolResult({ content: "Error: could not read plan file." });
2422
- }
2423
- const { checkpoints, checked } = this._parsePlanCheckpoints(currentContent);
2424
- if (checkpoints.length === 0) {
2425
- return new ToolResult({ content: "Error: no checkpoints found in plan file." });
2426
- }
2427
- this._activePlanCheckpoints = checkpoints;
2428
- this._activePlanChecked = checked;
2429
- if (item < 0 || item >= checkpoints.length) {
2326
+ // Convert 1-based user index to 0-based internal index
2327
+ const idx = item - 1;
2328
+ if (idx < 0 || idx >= this._activePlanCheckpoints.length) {
2430
2329
  return new ToolResult({
2431
- content: `Error: 'item' index ${item} is out of range (0..${checkpoints.length - 1}).`,
2330
+ content: `Error: 'item' ${item} is out of range (1..${this._activePlanCheckpoints.length}).`,
2432
2331
  });
2433
2332
  }
2434
- this._activePlanChecked[item] = true;
2435
- // Update the file on disk: replace the matching unchecked item with checked
2436
- try {
2437
- const lines = currentContent.split("\n");
2438
- let checkpointIndex = 0;
2439
- let inCheckpointsSection = false;
2440
- for (let i = 0; i < lines.length; i++) {
2441
- if (/^## Checkpoints\b/.test(lines[i])) {
2442
- inCheckpointsSection = true;
2443
- continue;
2444
- }
2445
- if (inCheckpointsSection && /^## /.test(lines[i]))
2446
- break;
2447
- if (inCheckpointsSection && /^- \[[ x]\] .+$/.test(lines[i])) {
2448
- if (checkpointIndex === item) {
2449
- lines[i] = lines[i].replace(/^- \[ \]/, "- [x]");
2450
- break;
2451
- }
2452
- checkpointIndex++;
2453
- }
2454
- }
2455
- writeFileSync(this._activePlanFile, lines.join("\n"), "utf-8");
2456
- }
2457
- catch {
2458
- // File write failure is non-fatal — in-memory state is still updated
2333
+ const checkpointText = this._activePlanCheckpoints[idx];
2334
+ this._activePlanChecked[idx] = true;
2335
+ // Check if all checkpoints are now complete → auto-finish
2336
+ const allDone = this._activePlanChecked.every(Boolean);
2337
+ if (allDone) {
2338
+ // Clear state immediately (so persistence sees no plan on interrupt)
2339
+ // but delay TUI panel dismissal so the user sees the all-checked state
2340
+ this._emitPlanProgress("plan_update");
2341
+ this._activePlanCheckpoints = [];
2342
+ this._activePlanChecked = [];
2343
+ setTimeout(() => {
2344
+ this._emitPlanProgress("plan_finish");
2345
+ }, 5000);
2346
+ return new ToolResult({
2347
+ content: `✓ Checkpoint ${item} done: ${checkpointText}\n` +
2348
+ `All checkpoints complete. Plan closed.`,
2349
+ });
2459
2350
  }
2460
2351
  this._emitPlanProgress("plan_update");
2461
2352
  return new ToolResult({
2462
- content: `Checkpoint ${item} marked as done: ${checkpoints[item]}`,
2353
+ content: `✓ Checkpoint ${item} done: ${checkpointText}`,
2463
2354
  });
2464
2355
  }
2465
- // action === "finish"
2466
- this._activePlanFile = null;
2356
+ // action === "dismiss"
2467
2357
  this._activePlanCheckpoints = [];
2468
2358
  this._activePlanChecked = [];
2469
2359
  this._emitPlanProgress("plan_finish");
2470
- return new ToolResult({ content: "Plan finished and dismissed." });
2471
- }
2472
- _parsePlanCheckpoints(content) {
2473
- const checkpoints = [];
2474
- const checked = [];
2475
- let inCheckpointsSection = false;
2476
- for (const line of content.split("\n")) {
2477
- if (/^## Checkpoints\b/.test(line)) {
2478
- inCheckpointsSection = true;
2479
- continue;
2480
- }
2481
- if (inCheckpointsSection && /^## /.test(line))
2482
- break;
2483
- if (inCheckpointsSection) {
2484
- const match = line.match(/^- \[([x ])\] (.+)$/);
2485
- if (match) {
2486
- checkpoints.push(match[2]);
2487
- checked.push(match[1] === "x");
2488
- }
2489
- }
2490
- }
2491
- return { checkpoints, checked };
2360
+ return new ToolResult({ content: "Plan dismissed." });
2492
2361
  }
2493
2362
  _emitPlanProgress(action) {
2494
2363
  if (!this._progress)
@@ -2875,10 +2744,10 @@ export class Session {
2875
2744
  return undefined;
2876
2745
  const mc = this.primaryAgent.modelConfig;
2877
2746
  const provider = this.primaryAgent._provider;
2878
- const effectiveMax = this._effectiveMaxTokens(mc);
2747
+ const effectiveMax = mc.maxTokens;
2879
2748
  const budget = provider.budgetCalcMode === "full_context"
2880
- ? mc.contextLength
2881
- : mc.contextLength - effectiveMax;
2749
+ ? this._effectiveContextLength(mc)
2750
+ : this._effectiveContextLength(mc) - effectiveMax;
2882
2751
  if (budget <= 0)
2883
2752
  return undefined;
2884
2753
  const compactOutputRatio = this._thresholds.compact_output / 100;
@@ -2992,9 +2861,9 @@ export class Session {
2992
2861
  return;
2993
2862
  const mc = this.primaryAgent.modelConfig;
2994
2863
  const provider = this.primaryAgent._provider;
2995
- const effectiveMax = this._effectiveMaxTokens(mc);
2864
+ const effectiveMax = mc.maxTokens;
2996
2865
  const budget = provider.budgetCalcMode === "full_context"
2997
- ? mc.contextLength : mc.contextLength - effectiveMax;
2866
+ ? this._effectiveContextLength(mc) : this._effectiveContextLength(mc) - effectiveMax;
2998
2867
  if (budget <= 0)
2999
2868
  return;
3000
2869
  const ratio = this._lastInputTokens / budget;
@@ -3018,9 +2887,9 @@ export class Session {
3018
2887
  _updateHintStateAfterApiCall() {
3019
2888
  const mc = this.primaryAgent.modelConfig;
3020
2889
  const provider = this.primaryAgent._provider;
3021
- const effectiveMax = this._effectiveMaxTokens(mc);
2890
+ const effectiveMax = mc.maxTokens;
3022
2891
  const budget = provider.budgetCalcMode === "full_context"
3023
- ? mc.contextLength : mc.contextLength - effectiveMax;
2892
+ ? this._effectiveContextLength(mc) : this._effectiveContextLength(mc) - effectiveMax;
3024
2893
  if (budget <= 0)
3025
2894
  return;
3026
2895
  const ratio = this._lastInputTokens / budget;
@@ -3914,9 +3783,6 @@ export class Session {
3914
3783
  "and conclusions.";
3915
3784
  }
3916
3785
  _getSubAgentModelConfig() {
3917
- const name = this.config.subAgentModelName;
3918
- if (name)
3919
- return this.config.getModel(name);
3920
3786
  return this.primaryAgent.modelConfig;
3921
3787
  }
3922
3788
  _buildSubAgentContext(includeImportantLog) {
@@ -4203,10 +4069,10 @@ export class Session {
4203
4069
  _buildSubAgentCompactCheck(agent) {
4204
4070
  const mc = agent.modelConfig;
4205
4071
  const provider = agent._provider;
4206
- const effectiveMax = this._effectiveMaxTokens(mc);
4072
+ const effectiveMax = mc.maxTokens;
4207
4073
  const budget = provider.budgetCalcMode === "full_context"
4208
- ? mc.contextLength
4209
- : mc.contextLength - effectiveMax;
4074
+ ? this._effectiveContextLength(mc)
4075
+ : this._effectiveContextLength(mc) - effectiveMax;
4210
4076
  if (budget <= 0)
4211
4077
  return undefined;
4212
4078
  const compactOutputRatio = this._thresholds.compact_output / 100;