@falkenslab/agent-kit 0.10.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -97
- package/dist/core/agentSpec.d.ts +31 -1
- package/dist/core/hooks/stepGate.js +3 -2
- package/dist/core/language.d.ts +39 -0
- package/dist/core/language.js +59 -0
- package/dist/core/messages/de.d.ts +2 -0
- package/dist/core/messages/de.js +103 -0
- package/dist/core/messages/en.d.ts +90 -0
- package/dist/core/messages/en.js +102 -0
- package/dist/core/messages/es.d.ts +2 -0
- package/dist/core/messages/es.js +103 -0
- package/dist/core/messages/fr.d.ts +2 -0
- package/dist/core/messages/fr.js +103 -0
- package/dist/core/messages/index.d.ts +23 -0
- package/dist/core/messages/index.js +67 -0
- package/dist/core/runs.d.ts +51 -0
- package/dist/core/runs.js +194 -0
- package/dist/core/session.d.ts +8 -0
- package/dist/core/session.js +42 -7
- package/dist/core/toolLabels.d.ts +2 -1
- package/dist/core/toolLabels.js +31 -51
- package/dist/core/tools/humanApproval.js +3 -2
- package/dist/index.d.ts +5 -0
- package/dist/index.js +6 -0
- package/dist/tui/chatTui.d.ts +23 -2
- package/dist/tui/chatTui.js +71 -7
- package/dist/tui/claudeAuth.d.ts +3 -1
- package/dist/tui/claudeAuth.js +12 -17
- package/dist/tui/consoleRenderer.js +2 -1
- package/dist/tui/ink/PromptInput.d.ts +3 -4
- package/dist/tui/ink/PromptInput.js +30 -17
- package/dist/tui/ink/SessionView.js +16 -15
- package/dist/tui/ink/inkInteraction.js +4 -7
- package/dist/tui/ink/progressView.d.ts +6 -0
- package/dist/tui/ink/progressView.js +2 -0
- package/dist/tui/ink/promptEditing.d.ts +11 -5
- package/dist/tui/ink/promptEditing.js +28 -3
- package/dist/tui/ink/runChatInk.d.ts +25 -2
- package/dist/tui/ink/runChatInk.js +156 -48
- package/dist/tui/ink/sessionModel.d.ts +10 -0
- package/dist/tui/ink/sessionModel.js +27 -2
- package/dist/tui/ink/toolGroup.d.ts +2 -1
- package/dist/tui/ink/toolGroup.js +5 -18
- package/dist/tui/ink/wizard.d.ts +6 -0
- package/dist/tui/ink/wizard.js +4 -1
- package/dist/tui/language.d.ts +5 -0
- package/dist/tui/language.js +11 -0
- package/dist/tui/runs.d.ts +27 -0
- package/dist/tui/runs.js +30 -0
- package/dist/tui/terminalInteraction.js +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
const plural = (count, one, many) => (count === 1 ? one : many);
|
|
2
|
+
export const en = {
|
|
3
|
+
thinking: "Thinking…",
|
|
4
|
+
escToInterrupt: "esc to interrupt",
|
|
5
|
+
workedFor: (seconds) => `Worked for ${seconds}s`,
|
|
6
|
+
turns: (count) => `${count} ${plural(count, "turn", "turns")}`,
|
|
7
|
+
tokens: (input, output) => `${input} in / ${output} out`,
|
|
8
|
+
context: (percent) => `context ${percent}%`,
|
|
9
|
+
mode: (mode) => mode,
|
|
10
|
+
switchModeKey: "shift+tab",
|
|
11
|
+
modeLocked: (mode) => (mode ? `(${mode} mode can't be switched during the session)` : "(the mode can't be switched during the session)"),
|
|
12
|
+
linesBelow: (count) => `↓ ${count} more ${plural(count, "line", "lines")} (Ctrl+End)`,
|
|
13
|
+
copiedCharacters: (count) => `copied ${count} ${plural(count, "character", "characters")}`,
|
|
14
|
+
queued: (line) => `queued: ${line}`,
|
|
15
|
+
interrupted: "(interrupted)",
|
|
16
|
+
copiedReply: (characters) => `(copied the last reply: ${characters} characters)`,
|
|
17
|
+
nothingToCopy: "(nothing to copy yet)",
|
|
18
|
+
unknownCommand: (command) => `Unknown command: /${command}`,
|
|
19
|
+
mcpFailed: (servers) => `Some MCP servers failed to connect: ${servers}`,
|
|
20
|
+
resumeTitle: "Resume a conversation",
|
|
21
|
+
resumeHint: "↑/↓ choose · Enter resume · Esc cancel",
|
|
22
|
+
resumeQuestion: "Number of the conversation to resume (Enter to cancel): ",
|
|
23
|
+
noEarlierRuns: "(no earlier conversations to resume)",
|
|
24
|
+
currentRun: "(this one)",
|
|
25
|
+
resumed: (date) => `(resumed the conversation of ${date})`,
|
|
26
|
+
suggestionKey: "tab",
|
|
27
|
+
reverseSearch: (query) => `(reverse-i-search)'${query}': `,
|
|
28
|
+
pastedText: (id, extraLines) => `[Pasted text #${id} +${extraLines} ${plural(extraLines, "line", "lines")}]`,
|
|
29
|
+
shortcuts: [
|
|
30
|
+
"Enter send · \\ + Enter or Ctrl+J new line · Tab complete or take the suggestion",
|
|
31
|
+
"↑/↓ history · Ctrl+R search history · ↑ on an empty prompt edits the last queued message",
|
|
32
|
+
"Ctrl+W delete word · Ctrl+K to line end · Ctrl+U clear · Ctrl+←/→ move by word",
|
|
33
|
+
"@ mention a file · /copy copy the last reply · Shift+Tab switch mode · Ctrl+O unfold tool calls",
|
|
34
|
+
"Esc interrupt · Ctrl+C interrupt or exit · PgUp/PgDn or wheel scroll · Ctrl+End bottom",
|
|
35
|
+
"Drag to select, right-click to copy (full screen)",
|
|
36
|
+
"/resume resume an earlier conversation · --continue on the command line resumes the latest one",
|
|
37
|
+
],
|
|
38
|
+
moreLines: (count) => `… (${count} more lines)`,
|
|
39
|
+
proceed: "Do you want to proceed?",
|
|
40
|
+
yes: "Yes",
|
|
41
|
+
no: "No",
|
|
42
|
+
stop: "Stop",
|
|
43
|
+
doneContinue: "Done, continue",
|
|
44
|
+
approved: "✔ Approved",
|
|
45
|
+
rejected: "✘ Rejected",
|
|
46
|
+
stopped: "■ Stopped",
|
|
47
|
+
done: "✔ Done",
|
|
48
|
+
answeredByFile: "(answered through the response file)",
|
|
49
|
+
terminalQuestion: "Allow this to continue? [Y/n/q] ",
|
|
50
|
+
proposedAction: "Proposed action",
|
|
51
|
+
toolLine: (tool) => `Tool: ${tool}`,
|
|
52
|
+
parametersLine: (parameters) => `Parameters: ${parameters}`,
|
|
53
|
+
approvalTitle: "Human confirmation required before publishing",
|
|
54
|
+
summaryLine: (summary) => `Summary: ${summary}`,
|
|
55
|
+
toolPhrases: {
|
|
56
|
+
Read: ["read {n} file", "read {n} files"],
|
|
57
|
+
Write: ["wrote {n} file", "wrote {n} files"],
|
|
58
|
+
Edit: ["edited {n} file", "edited {n} files"],
|
|
59
|
+
Glob: ["listed files", "listed files {n} times"],
|
|
60
|
+
Grep: ["searched {n} time", "searched {n} times"],
|
|
61
|
+
Bash: ["ran {n} shell command", "ran {n} shell commands"],
|
|
62
|
+
WebSearch: ["searched the web", "searched the web {n} times"],
|
|
63
|
+
WebFetch: ["fetched {n} page", "fetched {n} pages"],
|
|
64
|
+
Skill: ["used {n} skill", "used {n} skills"],
|
|
65
|
+
Agent: ["ran {n} subagent", "ran {n} subagents"],
|
|
66
|
+
Task: ["ran {n} subagent", "ran {n} subagents"],
|
|
67
|
+
},
|
|
68
|
+
otherTools: ["used {n} tool", "used {n} tools"],
|
|
69
|
+
noOutput: "(no output)",
|
|
70
|
+
error: "error",
|
|
71
|
+
moreResultLines: (count) => `(+${count} ${plural(count, "line", "lines")})`,
|
|
72
|
+
earlierCalls: (count) => `… ${count} earlier`,
|
|
73
|
+
labels: {
|
|
74
|
+
askingApproval: (summary) => `Asking for human approval: ${summary}`,
|
|
75
|
+
waitingManual: "Waiting for a human to intervene manually",
|
|
76
|
+
savingToSources: (destination) => `Saving a file to sources/${destination}`,
|
|
77
|
+
reading: (file) => `Reading ${file}`,
|
|
78
|
+
writing: (file) => `Writing to ${file}`,
|
|
79
|
+
editing: (file) => `Editing ${file}`,
|
|
80
|
+
aFile: "a file",
|
|
81
|
+
findingFiles: (pattern) => `Searching for files matching "${pattern}"`,
|
|
82
|
+
searchingContents: (pattern) => `Searching file contents for "${pattern}"`,
|
|
83
|
+
running: (command) => `Running "${command}"`,
|
|
84
|
+
delegating: (subagent) => `Delegating to "${subagent}"`,
|
|
85
|
+
delegatingTask: (subagent, description) => `Delegating to "${subagent}": ${description}`,
|
|
86
|
+
aSubagent: "a subagent",
|
|
87
|
+
fetching: (url) => `Fetching ${url}`,
|
|
88
|
+
aPage: "a page",
|
|
89
|
+
searchingWeb: (query) => `Searching the web for "${query}"`,
|
|
90
|
+
applyingSkill: (skill) => `Applying the "${skill}" skill`,
|
|
91
|
+
aSkill: "a skill",
|
|
92
|
+
},
|
|
93
|
+
auth: {
|
|
94
|
+
noToken: "No Claude authentication token was found",
|
|
95
|
+
noTokenDetail: "(neither the CLAUDE_CODE_OAUTH_TOKEN environment variable, nor one passed in).",
|
|
96
|
+
generateQuestion: 'Generate one now with "claude setup-token" (requires a Claude Pro/Max subscription)?',
|
|
97
|
+
cantStart: "The agent can't start without a token. Set CLAUDE_CODE_OAUTH_TOKEN by hand (or run this again and accept generating it) and try again.",
|
|
98
|
+
generatingHeading: "=== Generating a Claude authentication token ===",
|
|
99
|
+
browserWillOpen: "A browser will open to log in; follow the instructions the command itself prints below.",
|
|
100
|
+
generated: "Token generated for this run. Save it yourself (e.g. CLAUDE_CODE_OAUTH_TOKEN in your shell profile) to skip this next time.",
|
|
101
|
+
},
|
|
102
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const plural = (count, one, many) => (count === 1 ? one : many);
|
|
2
|
+
const MODES = { autonomous: "autónomo", guided: "guiado", interactive: "interactivo" };
|
|
3
|
+
export const es = {
|
|
4
|
+
thinking: "Pensando…",
|
|
5
|
+
escToInterrupt: "esc para interrumpir",
|
|
6
|
+
workedFor: (seconds) => `Trabajó ${seconds}s`,
|
|
7
|
+
turns: (count) => `${count} ${plural(count, "turno", "turnos")}`,
|
|
8
|
+
tokens: (input, output) => `${input} ent. / ${output} sal.`,
|
|
9
|
+
context: (percent) => `contexto ${percent}%`,
|
|
10
|
+
mode: (mode) => MODES[mode],
|
|
11
|
+
switchModeKey: "⇧tab",
|
|
12
|
+
modeLocked: (mode) => (mode ? `(el modo ${MODES[mode]} no se puede cambiar durante la sesión)` : "(el modo no se puede cambiar durante la sesión)"),
|
|
13
|
+
linesBelow: (count) => `↓ ${count} ${plural(count, "línea más", "líneas más")} (Ctrl+Fin)`,
|
|
14
|
+
copiedCharacters: (count) => `${count} ${plural(count, "carácter copiado", "caracteres copiados")}`,
|
|
15
|
+
queued: (line) => `en cola: ${line}`,
|
|
16
|
+
interrupted: "(interrumpido)",
|
|
17
|
+
copiedReply: (characters) => `(copiada la última respuesta: ${characters} caracteres)`,
|
|
18
|
+
nothingToCopy: "(aún no hay nada que copiar)",
|
|
19
|
+
unknownCommand: (command) => `Comando desconocido: /${command}`,
|
|
20
|
+
mcpFailed: (servers) => `Algunos servidores MCP no se han podido conectar: ${servers}`,
|
|
21
|
+
resumeTitle: "Retomar una conversación",
|
|
22
|
+
resumeHint: "↑/↓ elegir · Intro retomar · Esc cancelar",
|
|
23
|
+
resumeQuestion: "Número de la conversación que retomar (Intro para cancelar): ",
|
|
24
|
+
noEarlierRuns: "(no hay conversaciones anteriores que retomar)",
|
|
25
|
+
currentRun: "(esta)",
|
|
26
|
+
resumed: (date) => `(retomada la conversación del ${date})`,
|
|
27
|
+
suggestionKey: "tab",
|
|
28
|
+
reverseSearch: (query) => `(búsqueda inversa)'${query}': `,
|
|
29
|
+
pastedText: (id, extraLines) => `[Texto pegado #${id} +${extraLines} ${plural(extraLines, "línea", "líneas")}]`,
|
|
30
|
+
shortcuts: [
|
|
31
|
+
"Intro enviar · \\ + Intro o Ctrl+J nueva línea · Tab completar o aceptar la sugerencia",
|
|
32
|
+
"↑/↓ historial · Ctrl+R buscar en el historial · ↑ con el prompt vacío edita el último mensaje en cola",
|
|
33
|
+
"Ctrl+W borrar palabra · Ctrl+K hasta el final · Ctrl+U vaciar · Ctrl+←/→ moverse por palabras",
|
|
34
|
+
"@ mencionar un fichero · /copy copiar la última respuesta · Mayús+Tab cambiar de modo · Ctrl+O desplegar herramientas",
|
|
35
|
+
"Esc interrumpir · Ctrl+C interrumpir o salir · RePág/AvPág o rueda desplazarse · Ctrl+Fin abajo del todo",
|
|
36
|
+
"Arrastrar para seleccionar, clic derecho para copiar (pantalla completa)",
|
|
37
|
+
"/resume retomar una conversación anterior · --continue al arrancar retoma la última",
|
|
38
|
+
],
|
|
39
|
+
moreLines: (count) => `… (${count} líneas más)`,
|
|
40
|
+
proceed: "¿Quieres continuar?",
|
|
41
|
+
yes: "Sí",
|
|
42
|
+
no: "No",
|
|
43
|
+
stop: "Parar",
|
|
44
|
+
doneContinue: "Hecho, continuar",
|
|
45
|
+
approved: "✔ Aprobado",
|
|
46
|
+
rejected: "✘ Rechazado",
|
|
47
|
+
stopped: "■ Parado",
|
|
48
|
+
done: "✔ Hecho",
|
|
49
|
+
answeredByFile: "(respondido con el fichero de respuesta)",
|
|
50
|
+
terminalQuestion: "¿Permitir que continúe? [Y/n/q] ",
|
|
51
|
+
proposedAction: "Acción propuesta",
|
|
52
|
+
toolLine: (tool) => `Herramienta: ${tool}`,
|
|
53
|
+
parametersLine: (parameters) => `Parámetros: ${parameters}`,
|
|
54
|
+
approvalTitle: "Hace falta confirmación humana antes de publicar",
|
|
55
|
+
summaryLine: (summary) => `Resumen: ${summary}`,
|
|
56
|
+
toolPhrases: {
|
|
57
|
+
Read: ["leyó {n} fichero", "leyó {n} ficheros"],
|
|
58
|
+
Write: ["escribió {n} fichero", "escribió {n} ficheros"],
|
|
59
|
+
Edit: ["editó {n} fichero", "editó {n} ficheros"],
|
|
60
|
+
Glob: ["listó ficheros", "listó ficheros {n} veces"],
|
|
61
|
+
Grep: ["buscó {n} vez", "buscó {n} veces"],
|
|
62
|
+
Bash: ["ejecutó {n} comando", "ejecutó {n} comandos"],
|
|
63
|
+
WebSearch: ["buscó en la web", "buscó en la web {n} veces"],
|
|
64
|
+
WebFetch: ["abrió {n} página", "abrió {n} páginas"],
|
|
65
|
+
Skill: ["usó {n} skill", "usó {n} skills"],
|
|
66
|
+
Agent: ["lanzó {n} subagente", "lanzó {n} subagentes"],
|
|
67
|
+
Task: ["lanzó {n} subagente", "lanzó {n} subagentes"],
|
|
68
|
+
},
|
|
69
|
+
otherTools: ["usó {n} herramienta", "usó {n} herramientas"],
|
|
70
|
+
noOutput: "(sin salida)",
|
|
71
|
+
error: "error",
|
|
72
|
+
moreResultLines: (count) => `(+${count} ${plural(count, "línea", "líneas")})`,
|
|
73
|
+
earlierCalls: (count) => `… ${count} ${plural(count, "anterior", "anteriores")}`,
|
|
74
|
+
labels: {
|
|
75
|
+
askingApproval: (summary) => `Pidiendo aprobación humana: ${summary}`,
|
|
76
|
+
waitingManual: "Esperando a que una persona intervenga a mano",
|
|
77
|
+
savingToSources: (destination) => `Guardando un fichero en sources/${destination}`,
|
|
78
|
+
reading: (file) => `Leyendo ${file}`,
|
|
79
|
+
writing: (file) => `Escribiendo en ${file}`,
|
|
80
|
+
editing: (file) => `Editando ${file}`,
|
|
81
|
+
aFile: "un fichero",
|
|
82
|
+
findingFiles: (pattern) => `Buscando ficheros que coincidan con "${pattern}"`,
|
|
83
|
+
searchingContents: (pattern) => `Buscando "${pattern}" en los ficheros`,
|
|
84
|
+
running: (command) => `Ejecutando "${command}"`,
|
|
85
|
+
delegating: (subagent) => `Delegando en "${subagent}"`,
|
|
86
|
+
delegatingTask: (subagent, description) => `Delegando en "${subagent}": ${description}`,
|
|
87
|
+
aSubagent: "un subagente",
|
|
88
|
+
fetching: (url) => `Abriendo ${url}`,
|
|
89
|
+
aPage: "una página",
|
|
90
|
+
searchingWeb: (query) => `Buscando en la web "${query}"`,
|
|
91
|
+
applyingSkill: (skill) => `Aplicando la skill "${skill}"`,
|
|
92
|
+
aSkill: "una skill",
|
|
93
|
+
},
|
|
94
|
+
auth: {
|
|
95
|
+
noToken: "No se ha encontrado ningún token de autenticación de Claude",
|
|
96
|
+
noTokenDetail: "(ni la variable de entorno CLAUDE_CODE_OAUTH_TOKEN ni uno pasado por código).",
|
|
97
|
+
generateQuestion: '¿Generar uno ahora con "claude setup-token"? (requiere una suscripción Claude Pro/Max)',
|
|
98
|
+
cantStart: "El agente no puede arrancar sin un token. Define CLAUDE_CODE_OAUTH_TOKEN a mano (o vuelve a ejecutarlo y acepta generarlo) e inténtalo de nuevo.",
|
|
99
|
+
generatingHeading: "=== Generando un token de autenticación de Claude ===",
|
|
100
|
+
browserWillOpen: "Se abrirá un navegador para iniciar sesión; sigue las instrucciones que el propio comando muestra abajo.",
|
|
101
|
+
generated: "Token generado para esta ejecución. Guárdalo tú (p. ej. CLAUDE_CODE_OAUTH_TOKEN en el perfil de tu shell) para no repetir esto la próxima vez.",
|
|
102
|
+
},
|
|
103
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const plural = (count, one, many) => (count === 1 ? one : many);
|
|
2
|
+
const MODES = { autonomous: "autonome", guided: "guidé", interactive: "interactif" };
|
|
3
|
+
export const fr = {
|
|
4
|
+
thinking: "Réflexion…",
|
|
5
|
+
escToInterrupt: "échap pour interrompre",
|
|
6
|
+
workedFor: (seconds) => `A travaillé ${seconds}s`,
|
|
7
|
+
turns: (count) => `${count} ${plural(count, "tour", "tours")}`,
|
|
8
|
+
tokens: (input, output) => `${input} entr. / ${output} sort.`,
|
|
9
|
+
context: (percent) => `contexte ${percent}%`,
|
|
10
|
+
mode: (mode) => MODES[mode],
|
|
11
|
+
switchModeKey: "⇧tab",
|
|
12
|
+
modeLocked: (mode) => (mode ? `(le mode ${MODES[mode]} ne peut pas changer pendant la session)` : "(le mode ne peut pas changer pendant la session)"),
|
|
13
|
+
linesBelow: (count) => `↓ ${count} ${plural(count, "ligne de plus", "lignes de plus")} (Ctrl+Fin)`,
|
|
14
|
+
copiedCharacters: (count) => `${count} ${plural(count, "caractère copié", "caractères copiés")}`,
|
|
15
|
+
queued: (line) => `en attente : ${line}`,
|
|
16
|
+
interrupted: "(interrompu)",
|
|
17
|
+
copiedReply: (characters) => `(dernière réponse copiée : ${characters} caractères)`,
|
|
18
|
+
nothingToCopy: "(rien à copier pour l'instant)",
|
|
19
|
+
unknownCommand: (command) => `Commande inconnue : /${command}`,
|
|
20
|
+
mcpFailed: (servers) => `Certains serveurs MCP n'ont pas pu se connecter : ${servers}`,
|
|
21
|
+
resumeTitle: "Reprendre une conversation",
|
|
22
|
+
resumeHint: "↑/↓ choisir · Entrée reprendre · Échap annuler",
|
|
23
|
+
resumeQuestion: "Numéro de la conversation à reprendre (Entrée pour annuler) : ",
|
|
24
|
+
noEarlierRuns: "(aucune conversation précédente à reprendre)",
|
|
25
|
+
currentRun: "(celle-ci)",
|
|
26
|
+
resumed: (date) => `(conversation du ${date} reprise)`,
|
|
27
|
+
suggestionKey: "tab",
|
|
28
|
+
reverseSearch: (query) => `(recherche inverse)'${query}': `,
|
|
29
|
+
pastedText: (id, extraLines) => `[Texte collé #${id} +${extraLines} ${plural(extraLines, "ligne", "lignes")}]`,
|
|
30
|
+
shortcuts: [
|
|
31
|
+
"Entrée envoyer · \\ + Entrée ou Ctrl+J nouvelle ligne · Tab compléter ou accepter la suggestion",
|
|
32
|
+
"↑/↓ historique · Ctrl+R chercher dans l'historique · ↑ sur un prompt vide modifie le dernier message en attente",
|
|
33
|
+
"Ctrl+W effacer un mot · Ctrl+K jusqu'à la fin · Ctrl+U vider · Ctrl+←/→ par mots",
|
|
34
|
+
"@ mentionner un fichier · /copy copier la dernière réponse · Maj+Tab changer de mode · Ctrl+O déplier les outils",
|
|
35
|
+
"Échap interrompre · Ctrl+C interrompre ou quitter · PgPréc/PgSuiv ou molette défiler · Ctrl+Fin tout en bas",
|
|
36
|
+
"Glisser pour sélectionner, clic droit pour copier (plein écran)",
|
|
37
|
+
"/resume reprendre une conversation précédente · --continue au démarrage reprend la dernière",
|
|
38
|
+
],
|
|
39
|
+
moreLines: (count) => `… (${count} lignes de plus)`,
|
|
40
|
+
proceed: "Voulez-vous continuer ?",
|
|
41
|
+
yes: "Oui",
|
|
42
|
+
no: "Non",
|
|
43
|
+
stop: "Arrêter",
|
|
44
|
+
doneContinue: "Terminé, continuer",
|
|
45
|
+
approved: "✔ Approuvé",
|
|
46
|
+
rejected: "✘ Refusé",
|
|
47
|
+
stopped: "■ Arrêté",
|
|
48
|
+
done: "✔ Terminé",
|
|
49
|
+
answeredByFile: "(répondu par le fichier de réponse)",
|
|
50
|
+
terminalQuestion: "Autoriser la suite ? [Y/n/q] ",
|
|
51
|
+
proposedAction: "Action proposée",
|
|
52
|
+
toolLine: (tool) => `Outil : ${tool}`,
|
|
53
|
+
parametersLine: (parameters) => `Paramètres : ${parameters}`,
|
|
54
|
+
approvalTitle: "Confirmation humaine requise avant de publier",
|
|
55
|
+
summaryLine: (summary) => `Résumé : ${summary}`,
|
|
56
|
+
toolPhrases: {
|
|
57
|
+
Read: ["a lu {n} fichier", "a lu {n} fichiers"],
|
|
58
|
+
Write: ["a écrit {n} fichier", "a écrit {n} fichiers"],
|
|
59
|
+
Edit: ["a modifié {n} fichier", "a modifié {n} fichiers"],
|
|
60
|
+
Glob: ["a listé des fichiers", "a listé des fichiers {n} fois"],
|
|
61
|
+
Grep: ["a cherché {n} fois", "a cherché {n} fois"],
|
|
62
|
+
Bash: ["a exécuté {n} commande", "a exécuté {n} commandes"],
|
|
63
|
+
WebSearch: ["a cherché sur le web", "a cherché sur le web {n} fois"],
|
|
64
|
+
WebFetch: ["a ouvert {n} page", "a ouvert {n} pages"],
|
|
65
|
+
Skill: ["a utilisé {n} skill", "a utilisé {n} skills"],
|
|
66
|
+
Agent: ["a lancé {n} sous-agent", "a lancé {n} sous-agents"],
|
|
67
|
+
Task: ["a lancé {n} sous-agent", "a lancé {n} sous-agents"],
|
|
68
|
+
},
|
|
69
|
+
otherTools: ["a utilisé {n} outil", "a utilisé {n} outils"],
|
|
70
|
+
noOutput: "(aucune sortie)",
|
|
71
|
+
error: "erreur",
|
|
72
|
+
moreResultLines: (count) => `(+${count} ${plural(count, "ligne", "lignes")})`,
|
|
73
|
+
earlierCalls: (count) => `… ${count} ${plural(count, "précédent", "précédents")}`,
|
|
74
|
+
labels: {
|
|
75
|
+
askingApproval: (summary) => `Demande d'approbation humaine : ${summary}`,
|
|
76
|
+
waitingManual: "En attente d'une intervention manuelle",
|
|
77
|
+
savingToSources: (destination) => `Enregistrement d'un fichier dans sources/${destination}`,
|
|
78
|
+
reading: (file) => `Lecture de ${file}`,
|
|
79
|
+
writing: (file) => `Écriture dans ${file}`,
|
|
80
|
+
editing: (file) => `Modification de ${file}`,
|
|
81
|
+
aFile: "un fichier",
|
|
82
|
+
findingFiles: (pattern) => `Recherche des fichiers correspondant à "${pattern}"`,
|
|
83
|
+
searchingContents: (pattern) => `Recherche de "${pattern}" dans les fichiers`,
|
|
84
|
+
running: (command) => `Exécution de "${command}"`,
|
|
85
|
+
delegating: (subagent) => `Délégation à "${subagent}"`,
|
|
86
|
+
delegatingTask: (subagent, description) => `Délégation à "${subagent}" : ${description}`,
|
|
87
|
+
aSubagent: "un sous-agent",
|
|
88
|
+
fetching: (url) => `Ouverture de ${url}`,
|
|
89
|
+
aPage: "une page",
|
|
90
|
+
searchingWeb: (query) => `Recherche sur le web de "${query}"`,
|
|
91
|
+
applyingSkill: (skill) => `Application de la skill "${skill}"`,
|
|
92
|
+
aSkill: "une skill",
|
|
93
|
+
},
|
|
94
|
+
auth: {
|
|
95
|
+
noToken: "Aucun jeton d'authentification Claude n'a été trouvé",
|
|
96
|
+
noTokenDetail: "(ni la variable d'environnement CLAUDE_CODE_OAUTH_TOKEN, ni un jeton passé par le code).",
|
|
97
|
+
generateQuestion: 'En générer un maintenant avec "claude setup-token" ? (nécessite un abonnement Claude Pro/Max)',
|
|
98
|
+
cantStart: "L'agent ne peut pas démarrer sans jeton. Définissez CLAUDE_CODE_OAUTH_TOKEN à la main (ou relancez et acceptez de le générer), puis réessayez.",
|
|
99
|
+
generatingHeading: "=== Génération d'un jeton d'authentification Claude ===",
|
|
100
|
+
browserWillOpen: "Un navigateur va s'ouvrir pour vous connecter ; suivez les instructions que la commande affiche ci-dessous.",
|
|
101
|
+
generated: "Jeton généré pour cette exécution. Enregistrez-le vous-même (par ex. CLAUDE_CODE_OAUTH_TOKEN dans le profil de votre shell) pour éviter cette étape la prochaine fois.",
|
|
102
|
+
},
|
|
103
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type Language } from "../language.js";
|
|
2
|
+
import { type Messages } from "./en.js";
|
|
3
|
+
export type { Messages, ToolPhrase } from "./en.js";
|
|
4
|
+
/** A language's texts; any it lacks come from English (nested groups key by key). */
|
|
5
|
+
export declare function messagesFor(language: Language): Messages;
|
|
6
|
+
/** A catalog completed with the English text for every key it lacks. */
|
|
7
|
+
export declare function withEnglishFallback(own: Partial<Messages>): Messages;
|
|
8
|
+
/** Sets the language of the kit's texts directly (a host that resolved it itself, tests). */
|
|
9
|
+
export declare function setLanguage(language: Language): void;
|
|
10
|
+
/** The language of the kit's texts right now. */
|
|
11
|
+
export declare function getLanguage(): Language;
|
|
12
|
+
/**
|
|
13
|
+
* Resolves the language from `--language`, `option` and the system's (see language.ts) and
|
|
14
|
+
* makes it the kit's, the first time or whenever an option is given; otherwise keeps the one
|
|
15
|
+
* already chosen (so a chat started without `language` follows `buildSessionOptions()`'s
|
|
16
|
+
* `config.language`). Warnings for unsupported codes wait in `takeLanguageWarnings()`, each
|
|
17
|
+
* once per process.
|
|
18
|
+
*/
|
|
19
|
+
export declare function chooseLanguage(option?: string): Language;
|
|
20
|
+
/** Warnings about unsupported language codes not shown yet (English, one line each). */
|
|
21
|
+
export declare function takeLanguageWarnings(): string[];
|
|
22
|
+
/** The kit's texts in the current language. */
|
|
23
|
+
export declare function t(): Messages;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { detectLanguage } from "../language.js";
|
|
2
|
+
import { en } from "./en.js";
|
|
3
|
+
import { es } from "./es.js";
|
|
4
|
+
import { fr } from "./fr.js";
|
|
5
|
+
import { de } from "./de.js";
|
|
6
|
+
const CATALOGS = { en, es, fr, de };
|
|
7
|
+
/** A language's texts; any it lacks come from English (nested groups key by key). */
|
|
8
|
+
export function messagesFor(language) {
|
|
9
|
+
return withEnglishFallback(CATALOGS[language]);
|
|
10
|
+
}
|
|
11
|
+
/** A catalog completed with the English text for every key it lacks. */
|
|
12
|
+
export function withEnglishFallback(own) {
|
|
13
|
+
return {
|
|
14
|
+
...en,
|
|
15
|
+
...own,
|
|
16
|
+
labels: { ...en.labels, ...own.labels },
|
|
17
|
+
auth: { ...en.auth, ...own.auth },
|
|
18
|
+
toolPhrases: { ...en.toolPhrases, ...own.toolPhrases },
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
// One language per process: `--language` and the system's language are the process's, and
|
|
22
|
+
// an agent has one. English until something chooses (tests, a component used on its own).
|
|
23
|
+
let current = "en";
|
|
24
|
+
let messages = en;
|
|
25
|
+
let chosen = false;
|
|
26
|
+
let pendingWarnings = [];
|
|
27
|
+
const warned = new Set();
|
|
28
|
+
/** Sets the language of the kit's texts directly (a host that resolved it itself, tests). */
|
|
29
|
+
export function setLanguage(language) {
|
|
30
|
+
current = language;
|
|
31
|
+
messages = messagesFor(language);
|
|
32
|
+
chosen = true;
|
|
33
|
+
}
|
|
34
|
+
/** The language of the kit's texts right now. */
|
|
35
|
+
export function getLanguage() {
|
|
36
|
+
return current;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Resolves the language from `--language`, `option` and the system's (see language.ts) and
|
|
40
|
+
* makes it the kit's, the first time or whenever an option is given; otherwise keeps the one
|
|
41
|
+
* already chosen (so a chat started without `language` follows `buildSessionOptions()`'s
|
|
42
|
+
* `config.language`). Warnings for unsupported codes wait in `takeLanguageWarnings()`, each
|
|
43
|
+
* once per process.
|
|
44
|
+
*/
|
|
45
|
+
export function chooseLanguage(option) {
|
|
46
|
+
if (chosen && option === undefined)
|
|
47
|
+
return current;
|
|
48
|
+
const resolved = detectLanguage(option);
|
|
49
|
+
for (const warning of resolved.warnings) {
|
|
50
|
+
if (warned.has(warning))
|
|
51
|
+
continue;
|
|
52
|
+
warned.add(warning);
|
|
53
|
+
pendingWarnings.push(warning);
|
|
54
|
+
}
|
|
55
|
+
setLanguage(resolved.language);
|
|
56
|
+
return current;
|
|
57
|
+
}
|
|
58
|
+
/** Warnings about unsupported language codes not shown yet (English, one line each). */
|
|
59
|
+
export function takeLanguageWarnings() {
|
|
60
|
+
const taken = pendingWarnings;
|
|
61
|
+
pendingWarnings = [];
|
|
62
|
+
return taken;
|
|
63
|
+
}
|
|
64
|
+
/** The kit's texts in the current language. */
|
|
65
|
+
export function t() {
|
|
66
|
+
return messages;
|
|
67
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { SessionStore, SessionStoreEntry } from "@anthropic-ai/claude-agent-sdk";
|
|
2
|
+
/**
|
|
3
|
+
* One run of an agent: a folder under the agent's runs folder (`<runsDir>/<timestamp>/`)
|
|
4
|
+
* holding its session log, its transcript of tool calls and, with a run store, the SDK's
|
|
5
|
+
* own transcript of the conversation, so a person can resume it later.
|
|
6
|
+
*/
|
|
7
|
+
export interface RunFolder {
|
|
8
|
+
/** The run's folder. */
|
|
9
|
+
dir: string;
|
|
10
|
+
/** The SDK session kept in this folder (to resume it), or null for a new run. */
|
|
11
|
+
sessionId: string | null;
|
|
12
|
+
}
|
|
13
|
+
export interface RunSummary extends RunFolder {
|
|
14
|
+
sessionId: string;
|
|
15
|
+
/** When the run's conversation last changed. */
|
|
16
|
+
updatedAt: Date;
|
|
17
|
+
/** The human's last message, on one line (empty if none). */
|
|
18
|
+
lastMessage: string;
|
|
19
|
+
}
|
|
20
|
+
export interface ConversationMessage {
|
|
21
|
+
role: "user" | "assistant";
|
|
22
|
+
text: string;
|
|
23
|
+
}
|
|
24
|
+
/** A folder name for a new run, e.g. "2026-09-09T16-50-12-345Z" (sorts by date). */
|
|
25
|
+
export declare function runFolderName(date?: Date): string;
|
|
26
|
+
/** Creates a new, empty run folder under `runsDir`. */
|
|
27
|
+
export declare function createRunFolder(runsDir: string): Promise<RunFolder>;
|
|
28
|
+
/** The SDK session a run folder keeps, from its `session.json`, or null. */
|
|
29
|
+
export declare function readRunSession(dir: string): Promise<string | null>;
|
|
30
|
+
/**
|
|
31
|
+
* A `SessionStore` (the SDK's adapter for keeping transcripts elsewhere) that keeps one
|
|
32
|
+
* session in one run folder: `conversation.jsonl` for the main transcript,
|
|
33
|
+
* `subagents/*.jsonl` for each subagent's, and `session.json` with the session's id and
|
|
34
|
+
* which file is which subagent's. The SDK appends to it as the transcript grows and loads
|
|
35
|
+
* from it before resuming (confirmed empirically: a resumed session remembers the
|
|
36
|
+
* conversation, its subagents' transcripts included, even with the CLI's own copy deleted).
|
|
37
|
+
* The CLI still writes that copy under `~/.claude/projects/`: it can't be turned off while a
|
|
38
|
+
* store is in use.
|
|
39
|
+
*
|
|
40
|
+
* Bound to one folder, so every key it receives is taken as this run's session: one run
|
|
41
|
+
* folder is one conversation, resumed as many times as wanted (the session keeps its id).
|
|
42
|
+
*/
|
|
43
|
+
export declare function createRunStore(dir: string): SessionStore;
|
|
44
|
+
/** A transcript entry's text when it's something the human wrote or the agent replied, or null. */
|
|
45
|
+
export declare function entryText(entry: SessionStoreEntry): ConversationMessage | null;
|
|
46
|
+
/** The human's messages and the agent's replies in a run's conversation, in order. */
|
|
47
|
+
export declare function readConversation(dir: string): Promise<ConversationMessage[]>;
|
|
48
|
+
/** The runs under `runsDir` that kept a conversation, newest first; runs without `session.json` are left out. */
|
|
49
|
+
export declare function listRuns(runsDir: string): Promise<RunSummary[]>;
|
|
50
|
+
/** `--continue` among the process's arguments: start by resuming the latest run. */
|
|
51
|
+
export declare function continueArgument(argv: readonly string[]): boolean;
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { appendFile, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const CONVERSATION = "conversation.jsonl";
|
|
4
|
+
const SUBAGENTS = "subagents";
|
|
5
|
+
const SESSION = "session.json";
|
|
6
|
+
/** A folder name for a new run, e.g. "2026-09-09T16-50-12-345Z" (sorts by date). */
|
|
7
|
+
export function runFolderName(date = new Date()) {
|
|
8
|
+
return date.toISOString().replace(/[:.]/g, "-");
|
|
9
|
+
}
|
|
10
|
+
/** Creates a new, empty run folder under `runsDir`. */
|
|
11
|
+
export async function createRunFolder(runsDir) {
|
|
12
|
+
const dir = path.join(runsDir, runFolderName());
|
|
13
|
+
await mkdir(dir, { recursive: true });
|
|
14
|
+
return { dir, sessionId: null };
|
|
15
|
+
}
|
|
16
|
+
async function readSessionFile(dir) {
|
|
17
|
+
try {
|
|
18
|
+
const parsed = JSON.parse(await readFile(path.join(dir, SESSION), "utf8"));
|
|
19
|
+
return typeof parsed.sessionId === "string" ? { sessionId: parsed.sessionId, subagents: parsed.subagents ?? {} } : null;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** The SDK session a run folder keeps, from its `session.json`, or null. */
|
|
26
|
+
export async function readRunSession(dir) {
|
|
27
|
+
return (await readSessionFile(dir))?.sessionId ?? null;
|
|
28
|
+
}
|
|
29
|
+
async function readEntries(file) {
|
|
30
|
+
let raw;
|
|
31
|
+
try {
|
|
32
|
+
raw = await readFile(file, "utf8");
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (error.code === "ENOENT")
|
|
36
|
+
return null;
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
return raw
|
|
40
|
+
.split("\n")
|
|
41
|
+
.filter((line) => line.trim())
|
|
42
|
+
.map((line) => JSON.parse(line));
|
|
43
|
+
}
|
|
44
|
+
/** A readable, unused file name for a subagent's transcript, from its key (e.g. "subagents/agent-a67…"). */
|
|
45
|
+
function subagentFileName(subpath, taken) {
|
|
46
|
+
const name = subpath.replace(/^subagents\//, "").replace(/[^A-Za-z0-9._-]+/g, "_") || "agent";
|
|
47
|
+
let file = `${SUBAGENTS}/${name}.jsonl`;
|
|
48
|
+
for (let n = 2; taken.includes(file); n++)
|
|
49
|
+
file = `${SUBAGENTS}/${name}-${n}.jsonl`;
|
|
50
|
+
return file;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A `SessionStore` (the SDK's adapter for keeping transcripts elsewhere) that keeps one
|
|
54
|
+
* session in one run folder: `conversation.jsonl` for the main transcript,
|
|
55
|
+
* `subagents/*.jsonl` for each subagent's, and `session.json` with the session's id and
|
|
56
|
+
* which file is which subagent's. The SDK appends to it as the transcript grows and loads
|
|
57
|
+
* from it before resuming (confirmed empirically: a resumed session remembers the
|
|
58
|
+
* conversation, its subagents' transcripts included, even with the CLI's own copy deleted).
|
|
59
|
+
* The CLI still writes that copy under `~/.claude/projects/`: it can't be turned off while a
|
|
60
|
+
* store is in use.
|
|
61
|
+
*
|
|
62
|
+
* Bound to one folder, so every key it receives is taken as this run's session: one run
|
|
63
|
+
* folder is one conversation, resumed as many times as wanted (the session keeps its id).
|
|
64
|
+
*/
|
|
65
|
+
export function createRunStore(dir) {
|
|
66
|
+
const seen = new Set();
|
|
67
|
+
let session = null;
|
|
68
|
+
// Appends are chained: the SDK may call append() again before the last one has finished,
|
|
69
|
+
// and entries must keep their order.
|
|
70
|
+
let queue = Promise.resolve();
|
|
71
|
+
async function currentSession() {
|
|
72
|
+
session ??= await readSessionFile(dir);
|
|
73
|
+
return session;
|
|
74
|
+
}
|
|
75
|
+
async function saveSession(next) {
|
|
76
|
+
session = next;
|
|
77
|
+
await mkdir(dir, { recursive: true });
|
|
78
|
+
await writeFile(path.join(dir, SESSION), `${JSON.stringify(next, null, 2)}\n`, "utf8");
|
|
79
|
+
}
|
|
80
|
+
/** The file for a key; with `create`, registers the session (and a new subagent) in session.json. */
|
|
81
|
+
async function fileFor(key, create) {
|
|
82
|
+
const current = await currentSession();
|
|
83
|
+
if (!key.subpath) {
|
|
84
|
+
if (create && !current)
|
|
85
|
+
await saveSession({ sessionId: key.sessionId, subagents: {} });
|
|
86
|
+
return path.join(dir, CONVERSATION);
|
|
87
|
+
}
|
|
88
|
+
const known = current?.subagents[key.subpath];
|
|
89
|
+
if (known)
|
|
90
|
+
return path.join(dir, known);
|
|
91
|
+
if (!create)
|
|
92
|
+
return null;
|
|
93
|
+
const base = current ?? { sessionId: key.sessionId, subagents: {} };
|
|
94
|
+
const file = subagentFileName(key.subpath, Object.values(base.subagents));
|
|
95
|
+
await saveSession({ ...base, subagents: { ...base.subagents, [key.subpath]: file } });
|
|
96
|
+
return path.join(dir, file);
|
|
97
|
+
}
|
|
98
|
+
async function write(key, entries) {
|
|
99
|
+
// Retries and replays may send an entry again: its uuid, when it has one, says so.
|
|
100
|
+
const fresh = entries.filter((entry) => {
|
|
101
|
+
if (!entry.uuid)
|
|
102
|
+
return true;
|
|
103
|
+
const id = `${key.subpath ?? ""}:${entry.uuid}`;
|
|
104
|
+
if (seen.has(id))
|
|
105
|
+
return false;
|
|
106
|
+
seen.add(id);
|
|
107
|
+
return true;
|
|
108
|
+
});
|
|
109
|
+
if (fresh.length === 0)
|
|
110
|
+
return;
|
|
111
|
+
const file = (await fileFor(key, true));
|
|
112
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
113
|
+
await appendFile(file, fresh.map((entry) => `${JSON.stringify(entry)}\n`).join(""), "utf8");
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
append(key, entries) {
|
|
117
|
+
queue = queue.then(() => write(key, entries));
|
|
118
|
+
return queue;
|
|
119
|
+
},
|
|
120
|
+
async load(key) {
|
|
121
|
+
const file = await fileFor(key, false);
|
|
122
|
+
const entries = file ? await readEntries(file) : null;
|
|
123
|
+
// What's already there counts as seen: resuming must not append it twice.
|
|
124
|
+
for (const entry of entries ?? [])
|
|
125
|
+
if (entry.uuid)
|
|
126
|
+
seen.add(`${key.subpath ?? ""}:${entry.uuid}`);
|
|
127
|
+
return entries;
|
|
128
|
+
},
|
|
129
|
+
async listSubkeys() {
|
|
130
|
+
return Object.keys((await currentSession())?.subagents ?? {});
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/** A transcript entry's text when it's something the human wrote or the agent replied, or null. */
|
|
135
|
+
export function entryText(entry) {
|
|
136
|
+
if (entry.type !== "user" && entry.type !== "assistant")
|
|
137
|
+
return null;
|
|
138
|
+
if (entry.isMeta || entry.isSidechain)
|
|
139
|
+
return null;
|
|
140
|
+
const content = entry.message?.content;
|
|
141
|
+
let text = "";
|
|
142
|
+
if (typeof content === "string")
|
|
143
|
+
text = content;
|
|
144
|
+
else if (Array.isArray(content)) {
|
|
145
|
+
const parts = content;
|
|
146
|
+
// A user entry holding tool results isn't something the human wrote.
|
|
147
|
+
if (entry.type === "user" && parts.some((part) => part.type === "tool_result"))
|
|
148
|
+
return null;
|
|
149
|
+
text = parts
|
|
150
|
+
.filter((part) => part.type === "text")
|
|
151
|
+
.map((part) => part.text ?? "")
|
|
152
|
+
.join("");
|
|
153
|
+
}
|
|
154
|
+
// Slash commands and the CLI's own notes arrive as tagged text, not as the human's words.
|
|
155
|
+
if (!text.trim() || /^\s*<[a-z-]+>/.test(text))
|
|
156
|
+
return null;
|
|
157
|
+
return { role: entry.type, text };
|
|
158
|
+
}
|
|
159
|
+
/** The human's messages and the agent's replies in a run's conversation, in order. */
|
|
160
|
+
export async function readConversation(dir) {
|
|
161
|
+
const entries = (await readEntries(path.join(dir, CONVERSATION))) ?? [];
|
|
162
|
+
return entries.map(entryText).filter((message) => message !== null);
|
|
163
|
+
}
|
|
164
|
+
/** The runs under `runsDir` that kept a conversation, newest first; runs without `session.json` are left out. */
|
|
165
|
+
export async function listRuns(runsDir) {
|
|
166
|
+
let names;
|
|
167
|
+
try {
|
|
168
|
+
names = (await readdir(runsDir, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
return [];
|
|
172
|
+
}
|
|
173
|
+
const runs = [];
|
|
174
|
+
for (const name of names) {
|
|
175
|
+
const dir = path.join(runsDir, name);
|
|
176
|
+
const sessionId = await readRunSession(dir);
|
|
177
|
+
if (!sessionId)
|
|
178
|
+
continue;
|
|
179
|
+
const entries = (await readEntries(path.join(dir, CONVERSATION))) ?? [];
|
|
180
|
+
const times = entries.map((entry) => Date.parse(entry.timestamp ?? "")).filter((ms) => !Number.isNaN(ms));
|
|
181
|
+
const human = entries.map(entryText).filter((message) => message?.role === "user");
|
|
182
|
+
runs.push({
|
|
183
|
+
dir,
|
|
184
|
+
sessionId,
|
|
185
|
+
updatedAt: new Date(times.length > 0 ? Math.max(...times) : 0),
|
|
186
|
+
lastMessage: (human.at(-1)?.text ?? "").replace(/\s+/g, " ").trim(),
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
return runs.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
|
|
190
|
+
}
|
|
191
|
+
/** `--continue` among the process's arguments: start by resuming the latest run. */
|
|
192
|
+
export function continueArgument(argv) {
|
|
193
|
+
return argv.includes("--continue");
|
|
194
|
+
}
|