moderado 0.4.18 → 0.4.28

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/README.md CHANGED
@@ -88,7 +88,8 @@ npm install -g moderado
88
88
  moderado --help
89
89
  ```
90
90
 
91
- The latest published release is 0.4.10. Its `/connect` flow handles the
91
+ The 0.4.28 release candidate adds organized `/help` command groups and resumes
92
+ interrupted chat sessions. Its `/connect` flow handles the
92
93
  Gateway and direct providers; `/login` is no longer offered. The previous
93
94
  0.4.8 release uses `/login` for the Gateway. The earlier 0.3.10 release is
94
95
  self-contained and works without the Gateway. All three use the `moderado`
@@ -101,9 +102,7 @@ npm and Scoop. See `docs/DISTRIBUTION.md` for package-manager details.
101
102
 
102
103
  ### Update an existing installation
103
104
 
104
- These commands are available starting with Moderado v0.4.18. The currently
105
- published v0.4.10 release does not include them; install v0.4.18 or later with
106
- the original installer before using these commands.
105
+ These commands are available starting with Moderado v0.4.18.
107
106
 
108
107
  ```bash
109
108
  moderado update # latest release within the installed major version
package/dist/args.js CHANGED
@@ -103,7 +103,7 @@ USAGE:
103
103
  COMMANDS:
104
104
  models Discover live models, capability & access tiers
105
105
  run "<task>" Execute a bounded coding task in the workspace\n skills List built-in and user skills
106
- skills on|off NAME Enable or disable a user skill
106
+ skills on|off NAME|PATTERN Enable or disable user skills (* matches all)
107
107
  doctor Check local Moderado setup
108
108
  update Update within the current major version
109
109
  upgrade Upgrade across major versions
@@ -37,7 +37,7 @@ export declare function resolveTurnAssistantAnswer(result: {
37
37
  answer: string;
38
38
  isError: boolean;
39
39
  };
40
- export declare function saveFailedTurnPrompt(history: ChatMessage[], prompt: string): ChatMessage[];
40
+ export declare function saveFailedTurnPrompt(history: ChatMessage[], prompt: string, partialResponse?: string): ChatMessage[];
41
41
  export declare function isBareExitCommand(input: string): boolean;
42
42
  /** Resolve the required, explicit local destination for `/session share`. */
43
43
  export declare function resolveSessionSharePath(workspaceRoot: string, outputPath: string): string;
@@ -70,7 +70,6 @@ export declare function handleGenerationKeypress(str: string, key: {
70
70
  onBtw?: (command: string) => void;
71
71
  }): void;
72
72
  export declare function isNetworkCommand(request: Pick<ApprovalRequest, 'toolName' | 'exactPayload'>): boolean;
73
- export declare function isNetworkConsentReply(input: string, previousAnswer: string): boolean;
74
73
  export declare function isLocalModelQuery(input: string): boolean;
75
74
  export declare function isLocalProviderQuery(input: string): boolean;
76
75
  export declare function isLocalIdentityQuery(input: string): boolean;
@@ -101,7 +100,6 @@ export declare function buildSearchAnswerTask(question: string, result: ToolResu
101
100
  export declare function replaceEvidenceTurn(messages: ChatMessage[], evidenceTask: string, question: string): ChatMessage[];
102
101
  export interface ApprovalPolicyOptions {
103
102
  autoApprove: boolean;
104
- networkAccessApproved: boolean;
105
103
  requestInteractiveApproval: (request: ApprovalRequest, signal?: AbortSignal) => Promise<ApprovalDecision>;
106
104
  }
107
105
  export declare function decideApproval(request: ApprovalRequest, options: ApprovalPolicyOptions, signal?: AbortSignal): Promise<ApprovalDecision>;
@@ -1 +1 @@
1
- {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/commands/chat.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/commands/chat.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAkG,kBAAkB,EAA0D,MAAM,iBAAiB,CAAC;AAC7M,OAAO,KAAK,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACnF,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,WAAW,EAAoB,aAAa,EAAE,eAAe,EAAyB,UAAU,EAAmB,MAAM,qBAAqB,CAAC;AAC3L,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAuD,cAAc,EAAE,kBAAkB,EAA2K,MAAM,cAAc,CAAC;AAUhS,OAAO,EAAyH,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACtK,OAAO,EAAkC,kBAAkB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AASrG,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,kBAAkB,GAAG,aAAa,CAEtF;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,aAAa,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAazF;AASD,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAGjF;AAED,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,MAAM,CAE/D;AAED,wBAAgB,wBAAwB,IAAI,aAAa,CAQxD;AAED,eAAO,MAAM,uBAAuB,2KAiB1B,CAAC;AAEX,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAuBhE;AAED,MAAM,WAAW,kBAAkB;IACjC,cAAc,EAAE,OAAO,CAAC;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,kBAAkB,CA6D3E;AAED,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,EACd,UAAU,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,EAC1D,OAAO,EAAE,MAAM,EACf,gBAAgB,GAAE,MAAM,EAAO,GAC9B,MAAM,CASR;AAED,wBAAgB,0BAA0B,CACxC,MAAM,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,WAAW,EAAE,CAAC;IAAC,aAAa,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,EACnF,qBAAqB,EAAE,MAAM,EAC7B,cAAc,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EACnD,cAAc,SAAK,EACnB,YAAY,SAAY,EACxB,gBAAgB,GAAE,MAAM,EAAO,GAC9B;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAiBtC;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,WAAW,EAAE,EACtB,MAAM,EAAE,MAAM,EACd,eAAe,SAAK,GACnB,WAAW,EAAE,CAOf;AAoCD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAExD;AAED,6EAA6E;AAC7E,wBAAgB,uBAAuB,CAAC,aAAa,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAGzF;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAE/F;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,KAAK,CAAgB;IAC7B,OAAO,CAAC,KAAK,CAAM;IAEnB,IAAI,KAAK,IAAI,SAAS,MAAM,EAAE,CAE7B;IAED,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAO3B,KAAK,IAAI,MAAM,GAAG,SAAS;IAI3B,KAAK,IAAI,IAAI;IAKb,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAI5B,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAI/B,cAAc,IAAI,IAAI;IAMtB,WAAW,IAAI,MAAM,GAAG,SAAS;CASlC;AAED,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,MAAM,EACX,GAAG,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,EACnF,KAAK,EAAE,gBAAgB,EACvB,OAAO,EAAE;IACP,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC,GACA,IAAI,CAiDN;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,IAAI,CAAC,eAAe,EAAE,UAAU,GAAG,cAAc,CAAC,GAAG,OAAO,CAIrG;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAaxD;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAa3D;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAM3D;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAIxD;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAK1D;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAI5D;AAED,MAAM,WAAW,qBAAqB;IACpC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,qBAAqB,GAAG,MAAM,GAAG,SAAS,CA6BvG;AAED,4EAA4E;AAC5E,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAS/D;AAED,mFAAmF;AACnF,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,GAAG,SAAS,CASnF;AAKD,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,UAAU,EAAE,cAAc,UAAQ,GAAG,MAAM,CAQ9F;AACD,qFAAqF;AACrF,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,cAAc,GAAG,qBAAqB,GAAG,SAAS,CAGlG;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,cAAc,GAAG,oBAAoB,CAEpF;AAED,8GAA8G;AAC9G,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,MAAM,CAMlF;AAID,2GAA2G;AAC3G,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,WAAW,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,WAAW,EAAE,CAElH;AAED,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,OAAO,CAAC;IACrB,0BAA0B,EAAE,CAAC,OAAO,EAAE,eAAe,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAC;CAC3G;AAED,wBAAsB,cAAc,CAClC,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,qBAAqB,EAC9B,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,gBAAgB,CAAC,CAM3B;AAED,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,SAAS,EAAE,SAAS,GAAE,oBAAyB,GAAG,OAAO,CAAC,aAAa,CAAC,CAE9J;AAED,MAAM,WAAW,iBAAiB;IAChC,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAC5D,kBAAkB,CAAC,EAAE,OAAO,kBAAkB,CAAC;IAC/C,eAAe,CAAC,EAAE,OAAO,eAAe,CAAC;IACzC,kBAAkB,CAAC,EAAE,OAAO,kBAAkB,CAAC;CAChD;AAED,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,IAAI,EACzC,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,IAAI,CAAC,CA+Hf;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EAC9F,QAAQ,EAAE,aAAa,GAAG,SAAS,EACnC,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,UAAU,EAAE,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,EAC1E,MAAM,CAAC,EAAE,WAAW,EACpB,SAAS,CAAC,EAAE,WAAW,GACtB,OAAO,CAAC,IAAI,CAAC,CAoGf;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAogCnH"}
@@ -14,7 +14,7 @@ import { connectProviderInteractive, isAuthenticationFailure, isModeradoCloudOAu
14
14
  import { initWorkspace } from './init.js';
15
15
  import { expandMentions, createWorkspaceFileSource } from '../ui/file_mentions.js';
16
16
  import { listFiles } from '../vendor/tools/index.js';
17
- import { exitCleanly, promptInteractiveTurn, renderChatAnswerDelta, renderChatComposerCursor, renderChatThoughtTimeUpdate, renderChatUsageUpdate, renderGatewayFallbackUpdate, renderFullWelcomeScreen, renderWelcomePopupLayer, terminalCleanExitDone, wrapText } from '../ui/welcome.js';
17
+ import { exitCleanly, promptInteractiveTurn, renderChatAnswerDelta, renderChatComposerCursor, renderChatThoughtTimeUpdate, renderChatUsageUpdate, renderGatewayFallbackUpdate, renderFullWelcomeScreen, renderWelcomePopupLayer, terminalCleanExitDone, wrapText, getChatAnswerArea } from '../ui/welcome.js';
18
18
  import { accumulateSessionUsage, compactSessionMessages, createSession, exportSessionMarkdown, formatSessionCost, SessionStore } from '../sessions.js';
19
19
  import { layerPromptBox, renderBoxLines, selectConfirmPopup, selectListPopup } from '../ui/popup.js';
20
20
  import { askModalChoice } from '../ui/prompt.js';
@@ -23,6 +23,7 @@ import { inspectGitWorkspace, readGitDiff } from '../vendor/tools/index.js';
23
23
  import { buildWorkspaceMap } from '../repo_map.js';
24
24
  import { evaluateTokenBudget, parseBudgetCommand } from '../budget.js';
25
25
  import { discoverSkills, formatSkillContext, formatSkillsList, getEnabledSkillNames, selectEnabledSkills, setSkillEnabled } from '../skills.js';
26
+ import { getHelpCommandLines } from '../ui/help_content.js';
26
27
  export function createConnectionProvider(connection) {
27
28
  return new NvidiaAdapter({ apiKey: connection.apiKey, baseUrl: connection.baseUrl, providerId: connection.id, providerName: connection.displayName });
28
29
  }
@@ -192,10 +193,46 @@ export function resolveTurnAssistantAnswer(result, previousHistoryLength, lastEr
192
193
  isError: false,
193
194
  };
194
195
  }
195
- export function saveFailedTurnPrompt(history, prompt) {
196
+ export function saveFailedTurnPrompt(history, prompt, partialResponse = '') {
196
197
  if (!prompt.trim())
197
198
  return history;
198
- return [...history, { role: 'user', content: prompt }];
199
+ const interruptionNote = '[This response was interrupted before completion. Continue the task if asked.]';
200
+ const interruptedResponse = partialResponse.trim()
201
+ ? `${partialResponse.trim()}\n\n${interruptionNote}`
202
+ : '[This task was interrupted before a response was produced. Continue the task if asked.]';
203
+ return [...history, { role: 'user', content: prompt }, { role: 'assistant', content: interruptedResponse }];
204
+ }
205
+ const CONTINUE_INTERRUPTED_TASK_PROMPT = 'Continue the interrupted task from the saved conversation. Start from the unfinished work and avoid repeating completed steps.';
206
+ function hasInterruptedTurn(messages) {
207
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
208
+ const message = messages[index];
209
+ if (message.role !== 'assistant' || !message.content?.trim())
210
+ continue;
211
+ return message.content.includes('[This response was interrupted before completion.')
212
+ || message.content.includes('[This task was interrupted before a response was produced.');
213
+ }
214
+ return false;
215
+ }
216
+ function restoreLastSessionTurn(messages) {
217
+ let assistantIndex = -1;
218
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
219
+ if (messages[index].role === 'assistant' && messages[index].content?.trim()) {
220
+ assistantIndex = index;
221
+ break;
222
+ }
223
+ }
224
+ if (assistantIndex < 0) {
225
+ const question = [...messages].reverse().find((message) => message.role === 'user' && message.content?.trim())?.content ?? '';
226
+ return { question, answer: question ? 'This task was interrupted. Type “continue” to resume with the current model.' : '' };
227
+ }
228
+ let question = '';
229
+ for (let index = assistantIndex - 1; index >= 0; index -= 1) {
230
+ if (messages[index].role === 'user' && messages[index].content?.trim()) {
231
+ question = messages[index].content ?? '';
232
+ break;
233
+ }
234
+ }
235
+ return { question, answer: messages[assistantIndex].content ?? '' };
199
236
  }
200
237
  export function isBareExitCommand(input) {
201
238
  return input.trim().toLowerCase() === 'exit';
@@ -305,9 +342,6 @@ export function isNetworkCommand(request) {
305
342
  const text = Array.isArray(command) ? command.join(' ').toLowerCase() : '';
306
343
  return request.toolName === 'run_command' && /\b(curl|wget|invoke-webrequest|iwr|irm)\b|https?:\/\//.test(text);
307
344
  }
308
- export function isNetworkConsentReply(input, previousAnswer) {
309
- return /^(y|yes)$/i.test(input.trim()) && /\b(weather|internet|online|look up|web|curl)\b/i.test(previousAnswer);
310
- }
311
345
  export function isLocalModelQuery(input) {
312
346
  const text = input.trim().toLowerCase().replace(/[?!.]+$/, '').trim();
313
347
  if (!text)
@@ -455,9 +489,8 @@ export function replaceEvidenceTurn(messages, evidenceTask, question) {
455
489
  return messages.map((message) => (message.role === 'user' && message.content === evidenceTask ? { role: 'user', content: question } : message));
456
490
  }
457
491
  export async function decideApproval(request, options, signal) {
458
- if (isNetworkCommand(request) && !options.networkAccessApproved) {
459
- return { requestId: request.requestId, status: 'denied', reason: 'Network access requires an explicit Yes reply to the model first.' };
460
- }
492
+ if (isNetworkCommand(request))
493
+ return options.requestInteractiveApproval(request, signal);
461
494
  if (options.autoApprove && !request.toolName.startsWith('mcp.')) {
462
495
  return { requestId: request.requestId, status: 'approved' };
463
496
  }
@@ -743,6 +776,7 @@ export async function handleChatSession(args, version, signal) {
743
776
  modelId: currentModel,
744
777
  mode: activeMode,
745
778
  });
779
+ currentModel = args.model ?? (activeSession.providerId === activeConnection?.id ? activeSession.modelId : undefined) ?? currentModel;
746
780
  if (activeSession.usage.available &&
747
781
  !activeSession.usage.costKnown &&
748
782
  activeSession.providerId === activeConnection?.id &&
@@ -767,11 +801,9 @@ export async function handleChatSession(args, version, signal) {
767
801
  await reloadMcpTools();
768
802
  const terminalApproval = new TerminalApprovalHandler();
769
803
  const checkpoints = new WorkspaceCheckpointStore();
770
- let networkAccessApproved = false;
771
804
  let suspendGenerationInput;
772
805
  const approvalHandler = { requestApproval: async (req, sig) => decideApproval(req, {
773
806
  autoApprove: activeAutoApprove,
774
- networkAccessApproved,
775
807
  requestInteractiveApproval: async (request, approvalSignal) => {
776
808
  const resumeGenerationInput = suspendGenerationInput?.();
777
809
  try {
@@ -785,8 +817,10 @@ export async function handleChatSession(args, version, signal) {
785
817
  const router = new Router();
786
818
  const loop = new AgentLoop();
787
819
  let conversationHistory = [...activeSession.messages];
788
- let lastQuestion = '';
789
- let lastAnswer = '';
820
+ const restoredTurn = restoreLastSessionTurn(conversationHistory);
821
+ let lastQuestion = restoredTurn.question;
822
+ let lastAnswer = restoredTurn.answer;
823
+ let lastTurnDone = conversationHistory.length > 0 && !hasInterruptedTurn(conversationHistory);
790
824
  let lastThoughtTime = 0;
791
825
  let lastOutputTokenRate;
792
826
  let lastTokenUsage;
@@ -802,10 +836,17 @@ export async function handleChatSession(args, version, signal) {
802
836
  isFirst = false;
803
837
  }
804
838
  else {
839
+ let autoContinueRequested = false;
805
840
  const turn = await promptInteractiveTurn({
806
841
  model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(), workspace: canonicalWorkspace, version,
807
- usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, initialMode: activeMode, initialAutoApprove: activeAutoApprove, isFirstTurn: isFirst, signal, chatQuestion: lastQuestion || undefined, chatAnswer: lastAnswer || undefined, chatThoughtTime: lastThoughtTime, outputTokenRate: lastOutputTokenRate, tokenUsage: lastTokenUsage,
842
+ usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, initialMode: activeMode, initialAutoApprove: activeAutoApprove, isFirstTurn: isFirst, signal, chatQuestion: lastQuestion || undefined, chatAnswer: lastAnswer || undefined, chatThoughtTime: lastThoughtTime, chatTaskDone: lastTurnDone, outputTokenRate: lastOutputTokenRate, tokenUsage: lastTokenUsage,
808
843
  queuedCommands: commandQueue.items,
844
+ takeAutoContinuePrompt: () => {
845
+ if (!autoContinueRequested)
846
+ return undefined;
847
+ autoContinueRequested = false;
848
+ return CONTINUE_INTERRUPTED_TASK_PROMPT;
849
+ },
809
850
  onExit: exitMessage,
810
851
  questionHistory: conversationHistory.flatMap((message) => message.role === 'user' && message.content?.trim() ? [message.content] : []),
811
852
  onModelSelect: async (drawFrame) => {
@@ -828,15 +869,25 @@ export async function handleChatSession(args, version, signal) {
828
869
  activeConnection = { ...activeConnection, defaultModel: modelId };
829
870
  saveConnection(activeConnection);
830
871
  config = loadConfig();
872
+ activeSession.providerId = activeConnection.id;
873
+ activeSession.providerName = activeConnection.displayName;
874
+ activeSession.modelId = modelId;
875
+ sessionStore.save(activeSession);
876
+ autoContinueRequested = hasInterruptedTurn(activeSession.messages);
831
877
  }
832
878
  return modelId;
833
879
  }
834
880
  const selection = await selectModelOverlay({ apiKey: activeConnection.id === 'moderado-cloud' ? undefined : activeConnection.apiKey, currentModel, signal, saveSelectionByDefault: true, drawFrame });
835
- if (selection.modelId) {
881
+ if (selection.modelId && selection.modelId !== currentModel) {
836
882
  currentModel = selection.modelId;
837
883
  activeConnection = { ...activeConnection, defaultModel: selection.modelId };
838
884
  saveConnection(activeConnection);
839
885
  config = loadConfig();
886
+ activeSession.providerId = activeConnection.id;
887
+ activeSession.providerName = activeConnection.displayName;
888
+ activeSession.modelId = selection.modelId;
889
+ sessionStore.save(activeSession);
890
+ autoContinueRequested = hasInterruptedTurn(activeSession.messages);
840
891
  }
841
892
  return selection.modelId;
842
893
  },
@@ -966,15 +1017,17 @@ export async function handleChatSession(args, version, signal) {
966
1017
  onSession: async (command, drawFrame) => {
967
1018
  let action = command.slice('/session'.length).trim();
968
1019
  if (!action) {
969
- action = (await selectListPopup('Session', [
1020
+ const sessionActions = [
970
1021
  { label: 'New session', value: 'new', description: 'Start with an empty conversation.' },
971
1022
  { label: 'List or resume', value: 'resume', description: 'Load a saved session for this workspace.' },
1023
+ ...(hasInterruptedTurn(activeSession.messages) ? [{ label: 'Continue interrupted task', value: 'continue', description: 'Continue the last stopped task with the current model.' }] : []),
972
1024
  { label: 'Export transcript', value: 'export', description: 'Write a redacted Markdown transcript in this workspace.' },
973
1025
  { label: 'Undo latest agent change', value: 'undo', description: 'Restore the last checkpoint when safe.' },
974
1026
  { label: 'Redo latest agent change', value: 'redo', description: 'Reapply the last safely undone checkpoint.' },
975
1027
  { label: 'Share transcript', value: 'share', description: 'Choose an explicit workspace output path.' },
976
1028
  { label: 'Compact history', value: 'compact', description: 'Reduce older messages without calling a model.' },
977
- ], { drawFrame, signal })) ?? '';
1029
+ ];
1030
+ action = (await selectListPopup('Session', sessionActions, { drawFrame, signal })) ?? '';
978
1031
  }
979
1032
  if (action === 'new') {
980
1033
  activeSession = createSession(canonicalWorkspace, { providerId: activeConnection?.id, providerName: activeConnection?.displayName, modelId: currentModel, mode: activeMode });
@@ -988,6 +1041,14 @@ export async function handleChatSession(args, version, signal) {
988
1041
  sessionStore.save(activeSession);
989
1042
  return;
990
1043
  }
1044
+ if (action === 'continue') {
1045
+ if (!hasInterruptedTurn(activeSession.messages)) {
1046
+ drawFrame(renderBoxLines('Continue task', ['There is no interrupted task in this session.', '', 'Press Esc or Enter to return.'], 64));
1047
+ return;
1048
+ }
1049
+ autoContinueRequested = true;
1050
+ return;
1051
+ }
991
1052
  if (action === 'list' || action === 'resume') {
992
1053
  const sessions = sessionStore.listSessions(canonicalWorkspace);
993
1054
  const id = await selectListPopup('Saved sessions', sessions.map((entry) => ({
@@ -1000,8 +1061,12 @@ export async function handleChatSession(args, version, signal) {
1000
1061
  activeSession = loaded;
1001
1062
  conversationHistory = [...loaded.messages];
1002
1063
  sessionTokens = loaded.usage.totalTokens;
1003
- currentModel = loaded.modelId ?? currentModel;
1064
+ currentModel = loaded.providerId === activeConnection?.id ? loaded.modelId ?? currentModel : currentModel;
1004
1065
  activeMode = loaded.mode;
1066
+ const restored = restoreLastSessionTurn(conversationHistory);
1067
+ lastQuestion = restored.question;
1068
+ lastAnswer = restored.answer;
1069
+ lastTurnDone = conversationHistory.length > 0 && !hasInterruptedTurn(conversationHistory);
1005
1070
  }
1006
1071
  return;
1007
1072
  }
@@ -1201,14 +1266,14 @@ export async function handleChatSession(args, version, signal) {
1201
1266
  if ((parts[1] === 'on' || parts[1] === 'off') && parts[2]) {
1202
1267
  try {
1203
1268
  enabledSkillNames = setSkillEnabled(parts[2], parts[1] === 'on');
1204
- lastAnswer = `User skill ${parts[2]} ${parts[1] === 'on' ? 'enabled' : 'disabled'}.\n\n${formatSkillsList(discoveredSkills, true, undefined, enabledSkillNames)}`;
1269
+ lastAnswer = `User skill${parts[2].includes('*') ? 's matching' : ''} ${parts[2]} ${parts[1] === 'on' ? 'enabled' : 'disabled'}.\n\n${formatSkillsList(discoveredSkills, true, undefined, enabledSkillNames)}`;
1205
1270
  }
1206
1271
  catch (error) {
1207
1272
  lastAnswer = error instanceof Error ? error.message : String(error);
1208
1273
  }
1209
1274
  }
1210
1275
  else if (parts[1] === 'on' || parts[1] === 'off') {
1211
- lastAnswer = `Usage: /skills ${parts[1]} NAME`;
1276
+ lastAnswer = `Usage: /skills ${parts[1]} NAME|PATTERN (* matches all user skills)`;
1212
1277
  }
1213
1278
  else if (parts[1]) {
1214
1279
  const skill = discoveredSkills.find((item) => item.name === parts[1]);
@@ -1233,21 +1298,7 @@ export async function handleChatSession(args, version, signal) {
1233
1298
  }
1234
1299
  if (trimmed === '/help') {
1235
1300
  lastQuestion = trimmed;
1236
- lastAnswer = 'Available slash commands:\n' +
1237
- ' /connect - Choose a Gateway, hosted, or local provider\n' +
1238
- ' /model - Switch active AI model\n' +
1239
- ' /init - Scaffold AGENTS.md from workspace scan\n' +
1240
- ' /mcp - Manage local MCP servers\n' +
1241
- ' /session - Create, resume, undo, redo, share, export, or compact sessions\n' +
1242
- ' /queue - Add, inspect, or clear queued follow-up commands\n' +
1243
- ' /workflow - Inspect Git, build plans, or undo agent changes\n' +
1244
- ' /review - Review current workspace changes for bugs\n' +
1245
- ' /map - Show a compact workspace file map\n' +
1246
- ' /budget - Set a per-task token limit (/budget 5000, /budget off)\n' +
1247
- ' /skills - List skills; use /skills on NAME or /skills off NAME\n' +
1248
- ' /clear - Reset conversation memory\n' +
1249
- ' /help - Display commands, shortcuts & version\n' +
1250
- ' /exit - Exit Moderado';
1301
+ lastAnswer = 'Available slash commands:\n' + getHelpCommandLines().map((line) => line.includes(' - ') ? line : `\n${line}`).join('\n');
1251
1302
  lastThoughtTime = 0.001;
1252
1303
  lastOutputTokenRate = undefined;
1253
1304
  lastTokenUsage = undefined;
@@ -1264,7 +1315,6 @@ export async function handleChatSession(args, version, signal) {
1264
1315
  }
1265
1316
  const expandedTurn = await expandMentions(trimmed, createWorkspaceFileSource(canonicalWorkspace));
1266
1317
  const effectivePrompt = expandedTurn.text;
1267
- networkAccessApproved = isNetworkConsentReply(trimmed, lastAnswer);
1268
1318
  // 1. Instant local meta queries (model/provider status)
1269
1319
  const localMetaAnswer = resolveLocalMetaQuery(trimmed, {
1270
1320
  currentModel,
@@ -1405,18 +1455,30 @@ export async function handleChatSession(args, version, signal) {
1405
1455
  sessionTokens = activeSession.usage.totalTokens;
1406
1456
  usageRecorded = true;
1407
1457
  };
1458
+ let streamedAnswer = '';
1408
1459
  try {
1409
1460
  const requestAbort = new AbortController();
1410
1461
  const requestSignal = signal ? AbortSignal.any([signal, requestAbort.signal]) : requestAbort.signal;
1411
- let streamedAnswer = '';
1462
+ let lastSessionCheckpointAt = 0;
1463
+ const checkpointInterruptedTurn = () => {
1464
+ activeSession.messages = saveFailedTurnPrompt(conversationHistory, effectivePrompt, streamedAnswer);
1465
+ activeSession.providerId = activeConnection?.id;
1466
+ activeSession.providerName = activeConnection?.displayName;
1467
+ activeSession.modelId = currentModel;
1468
+ activeSession.mode = activeMode;
1469
+ sessionStore.save(activeSession);
1470
+ lastSessionCheckpointAt = Date.now();
1471
+ };
1412
1472
  lastAnswer = '';
1473
+ lastTurnDone = false;
1474
+ let taskStep;
1413
1475
  let firstAssistantDeltaAt;
1414
1476
  let outputTokenRate;
1415
1477
  let budgetWarningIssued = false;
1416
1478
  let budgetStopped = false;
1417
1479
  let lastUsagePaint = 0;
1418
1480
  const usedModels = new Set();
1419
- let answerPosition = { row: 15, column: 1 };
1481
+ let answerPosition = { row: 15, column: getChatAnswerArea(process.stdout.columns).startColumn };
1420
1482
  let lastErrorEvent;
1421
1483
  const redrawChatFrame = () => {
1422
1484
  const thoughtTime = Math.max(0.001, (Date.now() - startedAt) / 1000);
@@ -1433,6 +1495,7 @@ export async function handleChatSession(args, version, signal) {
1433
1495
  chatQuestion: trimmed,
1434
1496
  chatAnswer: displayAnswer,
1435
1497
  chatThoughtTime: thoughtTime,
1498
+ chatTaskDone: lastTurnDone,
1436
1499
  outputTokenRate,
1437
1500
  tokenUsage: turnUsage,
1438
1501
  budgetStatus: budgetStopped ? 'Budget reached' : budgetWarningIssued ? 'Budget ≥80%' : undefined,
@@ -1441,9 +1504,8 @@ export async function handleChatSession(args, version, signal) {
1441
1504
  }, process.stdout.rows));
1442
1505
  process.stdout.write(renderChatComposerCursor({ width: process.stdout.columns }, commandQueue.currentDraft.length));
1443
1506
  if (displayAnswer) {
1444
- const width = process.stdout.columns || 80;
1445
- const maxWidth = Math.max(40, width - 4);
1446
- answerPosition = renderChatAnswerDelta(displayAnswer, { row: 15, column: 1 }, maxWidth);
1507
+ const area = getChatAnswerArea(process.stdout.columns);
1508
+ answerPosition = renderChatAnswerDelta(displayAnswer, { row: 15, column: area.startColumn }, area.wrapWidth, area.startColumn);
1447
1509
  }
1448
1510
  };
1449
1511
  const onGenerationKeypress = (str, key) => {
@@ -1533,7 +1595,7 @@ export async function handleChatSession(args, version, signal) {
1533
1595
  redrawChatFrame();
1534
1596
  thinkingTimer = setInterval(() => {
1535
1597
  if (!firstAssistantDeltaAt && !turnUsage) {
1536
- process.stdout.write(renderChatThoughtTimeUpdate((Date.now() - startedAt) / 1000));
1598
+ process.stdout.write(renderChatThoughtTimeUpdate((Date.now() - startedAt) / 1000, taskStep, process.stdout.columns));
1537
1599
  }
1538
1600
  }, 400);
1539
1601
  const evidenceTask = liveSearch ? buildSearchAnswerTask(effectivePrompt, liveSearch) : undefined;
@@ -1545,6 +1607,11 @@ export async function handleChatSession(args, version, signal) {
1545
1607
  eventListener: (event) => {
1546
1608
  if (event.type === 'model_change')
1547
1609
  usedModels.add(event.newModelId);
1610
+ if (event.type === 'progress') {
1611
+ taskStep = { step: event.step, maxSteps: event.maxSteps ?? 0 };
1612
+ if (!firstAssistantDeltaAt)
1613
+ process.stdout.write(renderChatThoughtTimeUpdate((Date.now() - startedAt) / 1000, taskStep, process.stdout.columns));
1614
+ }
1548
1615
  if (event.type === 'gateway_status')
1549
1616
  process.stdout.write(renderGatewayFallbackUpdate(event, process.stdout.columns || 80));
1550
1617
  if (event.type === 'usage') {
@@ -1571,7 +1638,10 @@ export async function handleChatSession(args, version, signal) {
1571
1638
  clearInterval(thinkingTimer);
1572
1639
  }
1573
1640
  streamedAnswer += event.delta;
1574
- const renderedDelta = renderChatAnswerDelta(event.delta, answerPosition, process.stdout.columns || 80);
1641
+ if (Date.now() - lastSessionCheckpointAt >= 1_000)
1642
+ checkpointInterruptedTurn();
1643
+ const area = getChatAnswerArea(process.stdout.columns);
1644
+ const renderedDelta = renderChatAnswerDelta(event.delta, answerPosition, area.wrapWidth, area.startColumn);
1575
1645
  answerPosition = renderedDelta;
1576
1646
  process.stdout.write(renderedDelta.sequence);
1577
1647
  process.stdout.write(renderChatComposerCursor({ width: process.stdout.columns }, commandQueue.currentDraft.length));
@@ -1593,6 +1663,7 @@ export async function handleChatSession(args, version, signal) {
1593
1663
  });
1594
1664
  let result;
1595
1665
  try {
1666
+ checkpointInterruptedTurn();
1596
1667
  result = await runAgent();
1597
1668
  }
1598
1669
  catch (error) {
@@ -1668,7 +1739,7 @@ export async function handleChatSession(args, version, signal) {
1668
1739
  streamedAnswer = lastAnswer;
1669
1740
  }
1670
1741
  else if (resolution.isError) {
1671
- conversationHistory = saveFailedTurnPrompt(conversationHistory, effectivePrompt);
1742
+ conversationHistory = saveFailedTurnPrompt(conversationHistory, effectivePrompt, streamedAnswer);
1672
1743
  activeSession.messages = conversationHistory;
1673
1744
  activeSession.providerId = activeConnection?.id;
1674
1745
  activeSession.providerName = activeConnection?.displayName;
@@ -1688,6 +1759,7 @@ export async function handleChatSession(args, version, signal) {
1688
1759
  }
1689
1760
  if (activeMode === 'Plan')
1690
1761
  activePlan = lastAnswer;
1762
+ lastTurnDone = result.status === 'completed' && !budgetStopped && !resolution.isError;
1691
1763
  lastQuestion = trimmed;
1692
1764
  lastThoughtTime = Math.max(0.001, (Date.now() - startedAt) / 1000);
1693
1765
  lastOutputTokenRate = outputTokenRate;
@@ -1708,7 +1780,7 @@ export async function handleChatSession(args, version, signal) {
1708
1780
  ? ` (other configured providers: ${otherConnections.join(', ')})`
1709
1781
  : '';
1710
1782
  lastQuestion = trimmed;
1711
- conversationHistory = saveFailedTurnPrompt(conversationHistory, effectivePrompt);
1783
+ conversationHistory = saveFailedTurnPrompt(conversationHistory, effectivePrompt, streamedAnswer);
1712
1784
  activeSession.messages = conversationHistory;
1713
1785
  activeSession.providerId = activeConnection?.id;
1714
1786
  activeSession.providerName = activeConnection?.displayName;