moderado 0.4.26 → 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 +2 -1
- package/dist/commands/chat.d.ts +1 -1
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +96 -27
- package/dist/commands/chat.js.map +1 -1
- package/dist/ui/help_content.d.ts +17 -0
- package/dist/ui/help_content.d.ts.map +1 -0
- package/dist/ui/help_content.js +13 -0
- package/dist/ui/help_content.js.map +1 -0
- package/dist/ui/help_modal.d.ts.map +1 -1
- package/dist/ui/help_modal.js +5 -5
- package/dist/ui/help_modal.js.map +1 -1
- package/dist/ui/provider_connect.js +2 -2
- package/dist/ui/provider_connect.js.map +1 -1
- package/dist/ui/welcome.d.ts +2 -0
- package/dist/ui/welcome.d.ts.map +1 -1
- package/dist/ui/welcome.js +20 -16
- package/dist/ui/welcome.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -88,7 +88,8 @@ npm install -g moderado
|
|
|
88
88
|
moderado --help
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
-
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`
|
package/dist/commands/chat.d.ts
CHANGED
|
@@ -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;
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
1
|
+
{"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/commands/chat.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/chat.js
CHANGED
|
@@ -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
|
-
|
|
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';
|
|
@@ -739,6 +776,7 @@ export async function handleChatSession(args, version, signal) {
|
|
|
739
776
|
modelId: currentModel,
|
|
740
777
|
mode: activeMode,
|
|
741
778
|
});
|
|
779
|
+
currentModel = args.model ?? (activeSession.providerId === activeConnection?.id ? activeSession.modelId : undefined) ?? currentModel;
|
|
742
780
|
if (activeSession.usage.available &&
|
|
743
781
|
!activeSession.usage.costKnown &&
|
|
744
782
|
activeSession.providerId === activeConnection?.id &&
|
|
@@ -779,9 +817,10 @@ export async function handleChatSession(args, version, signal) {
|
|
|
779
817
|
const router = new Router();
|
|
780
818
|
const loop = new AgentLoop();
|
|
781
819
|
let conversationHistory = [...activeSession.messages];
|
|
782
|
-
|
|
783
|
-
let
|
|
784
|
-
let
|
|
820
|
+
const restoredTurn = restoreLastSessionTurn(conversationHistory);
|
|
821
|
+
let lastQuestion = restoredTurn.question;
|
|
822
|
+
let lastAnswer = restoredTurn.answer;
|
|
823
|
+
let lastTurnDone = conversationHistory.length > 0 && !hasInterruptedTurn(conversationHistory);
|
|
785
824
|
let lastThoughtTime = 0;
|
|
786
825
|
let lastOutputTokenRate;
|
|
787
826
|
let lastTokenUsage;
|
|
@@ -797,10 +836,17 @@ export async function handleChatSession(args, version, signal) {
|
|
|
797
836
|
isFirst = false;
|
|
798
837
|
}
|
|
799
838
|
else {
|
|
839
|
+
let autoContinueRequested = false;
|
|
800
840
|
const turn = await promptInteractiveTurn({
|
|
801
841
|
model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(), workspace: canonicalWorkspace, version,
|
|
802
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,
|
|
803
843
|
queuedCommands: commandQueue.items,
|
|
844
|
+
takeAutoContinuePrompt: () => {
|
|
845
|
+
if (!autoContinueRequested)
|
|
846
|
+
return undefined;
|
|
847
|
+
autoContinueRequested = false;
|
|
848
|
+
return CONTINUE_INTERRUPTED_TASK_PROMPT;
|
|
849
|
+
},
|
|
804
850
|
onExit: exitMessage,
|
|
805
851
|
questionHistory: conversationHistory.flatMap((message) => message.role === 'user' && message.content?.trim() ? [message.content] : []),
|
|
806
852
|
onModelSelect: async (drawFrame) => {
|
|
@@ -823,15 +869,25 @@ export async function handleChatSession(args, version, signal) {
|
|
|
823
869
|
activeConnection = { ...activeConnection, defaultModel: modelId };
|
|
824
870
|
saveConnection(activeConnection);
|
|
825
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);
|
|
826
877
|
}
|
|
827
878
|
return modelId;
|
|
828
879
|
}
|
|
829
880
|
const selection = await selectModelOverlay({ apiKey: activeConnection.id === 'moderado-cloud' ? undefined : activeConnection.apiKey, currentModel, signal, saveSelectionByDefault: true, drawFrame });
|
|
830
|
-
if (selection.modelId) {
|
|
881
|
+
if (selection.modelId && selection.modelId !== currentModel) {
|
|
831
882
|
currentModel = selection.modelId;
|
|
832
883
|
activeConnection = { ...activeConnection, defaultModel: selection.modelId };
|
|
833
884
|
saveConnection(activeConnection);
|
|
834
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);
|
|
835
891
|
}
|
|
836
892
|
return selection.modelId;
|
|
837
893
|
},
|
|
@@ -961,15 +1017,17 @@ export async function handleChatSession(args, version, signal) {
|
|
|
961
1017
|
onSession: async (command, drawFrame) => {
|
|
962
1018
|
let action = command.slice('/session'.length).trim();
|
|
963
1019
|
if (!action) {
|
|
964
|
-
|
|
1020
|
+
const sessionActions = [
|
|
965
1021
|
{ label: 'New session', value: 'new', description: 'Start with an empty conversation.' },
|
|
966
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.' }] : []),
|
|
967
1024
|
{ label: 'Export transcript', value: 'export', description: 'Write a redacted Markdown transcript in this workspace.' },
|
|
968
1025
|
{ label: 'Undo latest agent change', value: 'undo', description: 'Restore the last checkpoint when safe.' },
|
|
969
1026
|
{ label: 'Redo latest agent change', value: 'redo', description: 'Reapply the last safely undone checkpoint.' },
|
|
970
1027
|
{ label: 'Share transcript', value: 'share', description: 'Choose an explicit workspace output path.' },
|
|
971
1028
|
{ label: 'Compact history', value: 'compact', description: 'Reduce older messages without calling a model.' },
|
|
972
|
-
]
|
|
1029
|
+
];
|
|
1030
|
+
action = (await selectListPopup('Session', sessionActions, { drawFrame, signal })) ?? '';
|
|
973
1031
|
}
|
|
974
1032
|
if (action === 'new') {
|
|
975
1033
|
activeSession = createSession(canonicalWorkspace, { providerId: activeConnection?.id, providerName: activeConnection?.displayName, modelId: currentModel, mode: activeMode });
|
|
@@ -983,6 +1041,14 @@ export async function handleChatSession(args, version, signal) {
|
|
|
983
1041
|
sessionStore.save(activeSession);
|
|
984
1042
|
return;
|
|
985
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
|
+
}
|
|
986
1052
|
if (action === 'list' || action === 'resume') {
|
|
987
1053
|
const sessions = sessionStore.listSessions(canonicalWorkspace);
|
|
988
1054
|
const id = await selectListPopup('Saved sessions', sessions.map((entry) => ({
|
|
@@ -995,8 +1061,12 @@ export async function handleChatSession(args, version, signal) {
|
|
|
995
1061
|
activeSession = loaded;
|
|
996
1062
|
conversationHistory = [...loaded.messages];
|
|
997
1063
|
sessionTokens = loaded.usage.totalTokens;
|
|
998
|
-
currentModel = loaded.modelId ?? currentModel;
|
|
1064
|
+
currentModel = loaded.providerId === activeConnection?.id ? loaded.modelId ?? currentModel : currentModel;
|
|
999
1065
|
activeMode = loaded.mode;
|
|
1066
|
+
const restored = restoreLastSessionTurn(conversationHistory);
|
|
1067
|
+
lastQuestion = restored.question;
|
|
1068
|
+
lastAnswer = restored.answer;
|
|
1069
|
+
lastTurnDone = conversationHistory.length > 0 && !hasInterruptedTurn(conversationHistory);
|
|
1000
1070
|
}
|
|
1001
1071
|
return;
|
|
1002
1072
|
}
|
|
@@ -1228,21 +1298,7 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1228
1298
|
}
|
|
1229
1299
|
if (trimmed === '/help') {
|
|
1230
1300
|
lastQuestion = trimmed;
|
|
1231
|
-
lastAnswer = 'Available slash commands:\n' +
|
|
1232
|
-
' /connect - Choose a Gateway, hosted, or local provider\n' +
|
|
1233
|
-
' /model - Switch active AI model\n' +
|
|
1234
|
-
' /init - Scaffold AGENTS.md from workspace scan\n' +
|
|
1235
|
-
' /mcp - Manage local MCP servers\n' +
|
|
1236
|
-
' /session - Create, resume, undo, redo, share, export, or compact sessions\n' +
|
|
1237
|
-
' /queue - Add, inspect, or clear queued follow-up commands\n' +
|
|
1238
|
-
' /workflow - Inspect Git, build plans, or undo agent changes\n' +
|
|
1239
|
-
' /review - Review current workspace changes for bugs\n' +
|
|
1240
|
-
' /map - Show a compact workspace file map\n' +
|
|
1241
|
-
' /budget - Set a per-task token limit (/budget 5000, /budget off)\n' +
|
|
1242
|
-
' /skills - List skills; use /skills on|off NAME|PATTERN (* for all)\n' +
|
|
1243
|
-
' /clear - Reset conversation memory\n' +
|
|
1244
|
-
' /help - Display commands, shortcuts & version\n' +
|
|
1245
|
-
' /exit - Exit Moderado';
|
|
1301
|
+
lastAnswer = 'Available slash commands:\n' + getHelpCommandLines().map((line) => line.includes(' - ') ? line : `\n${line}`).join('\n');
|
|
1246
1302
|
lastThoughtTime = 0.001;
|
|
1247
1303
|
lastOutputTokenRate = undefined;
|
|
1248
1304
|
lastTokenUsage = undefined;
|
|
@@ -1399,10 +1455,20 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1399
1455
|
sessionTokens = activeSession.usage.totalTokens;
|
|
1400
1456
|
usageRecorded = true;
|
|
1401
1457
|
};
|
|
1458
|
+
let streamedAnswer = '';
|
|
1402
1459
|
try {
|
|
1403
1460
|
const requestAbort = new AbortController();
|
|
1404
1461
|
const requestSignal = signal ? AbortSignal.any([signal, requestAbort.signal]) : requestAbort.signal;
|
|
1405
|
-
let
|
|
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
|
+
};
|
|
1406
1472
|
lastAnswer = '';
|
|
1407
1473
|
lastTurnDone = false;
|
|
1408
1474
|
let taskStep;
|
|
@@ -1572,6 +1638,8 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1572
1638
|
clearInterval(thinkingTimer);
|
|
1573
1639
|
}
|
|
1574
1640
|
streamedAnswer += event.delta;
|
|
1641
|
+
if (Date.now() - lastSessionCheckpointAt >= 1_000)
|
|
1642
|
+
checkpointInterruptedTurn();
|
|
1575
1643
|
const area = getChatAnswerArea(process.stdout.columns);
|
|
1576
1644
|
const renderedDelta = renderChatAnswerDelta(event.delta, answerPosition, area.wrapWidth, area.startColumn);
|
|
1577
1645
|
answerPosition = renderedDelta;
|
|
@@ -1595,6 +1663,7 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1595
1663
|
});
|
|
1596
1664
|
let result;
|
|
1597
1665
|
try {
|
|
1666
|
+
checkpointInterruptedTurn();
|
|
1598
1667
|
result = await runAgent();
|
|
1599
1668
|
}
|
|
1600
1669
|
catch (error) {
|
|
@@ -1670,7 +1739,7 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1670
1739
|
streamedAnswer = lastAnswer;
|
|
1671
1740
|
}
|
|
1672
1741
|
else if (resolution.isError) {
|
|
1673
|
-
conversationHistory = saveFailedTurnPrompt(conversationHistory, effectivePrompt);
|
|
1742
|
+
conversationHistory = saveFailedTurnPrompt(conversationHistory, effectivePrompt, streamedAnswer);
|
|
1674
1743
|
activeSession.messages = conversationHistory;
|
|
1675
1744
|
activeSession.providerId = activeConnection?.id;
|
|
1676
1745
|
activeSession.providerName = activeConnection?.displayName;
|
|
@@ -1711,7 +1780,7 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1711
1780
|
? ` (other configured providers: ${otherConnections.join(', ')})`
|
|
1712
1781
|
: '';
|
|
1713
1782
|
lastQuestion = trimmed;
|
|
1714
|
-
conversationHistory = saveFailedTurnPrompt(conversationHistory, effectivePrompt);
|
|
1783
|
+
conversationHistory = saveFailedTurnPrompt(conversationHistory, effectivePrompt, streamedAnswer);
|
|
1715
1784
|
activeSession.messages = conversationHistory;
|
|
1716
1785
|
activeSession.providerId = activeConnection?.id;
|
|
1717
1786
|
activeSession.providerName = activeConnection?.displayName;
|