kimaki 0.26.0 → 0.28.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/dist/agent-model.e2e.test.js +250 -164
- package/dist/anthropic-account-identity.js +9 -1
- package/dist/anthropic-auth-plugin.js +10 -13
- package/dist/anthropic-auth-plugin.test.js +17 -0
- package/dist/anthropic-auth-state.js +1 -0
- package/dist/bash-tool-schema-plugin.js +32 -0
- package/dist/bash-tool-schema-plugin.test.js +49 -0
- package/dist/bin.js +73 -19
- package/dist/cli-commands/maintenance.js +46 -7
- package/dist/cli-commands/project.js +11 -27
- package/dist/cli-commands/send.js +30 -1
- package/dist/cli-commands/session.js +322 -90
- package/dist/cli-commands/task.js +34 -15
- package/dist/cli-parsing.test.js +83 -4
- package/dist/cli-runner.js +44 -8
- package/dist/cli-runner.test.js +27 -0
- package/dist/cli-send-thread.e2e.test.js +9 -9
- package/dist/cli.js +32 -0
- package/dist/commands/abort.js +5 -1
- package/dist/commands/action-buttons.js +2 -1
- package/dist/commands/add-project.js +4 -7
- package/dist/commands/agent.js +164 -32
- package/dist/commands/ask-question.js +23 -33
- package/dist/commands/ask-question.test.js +43 -1
- package/dist/commands/btw.js +28 -6
- package/dist/commands/file-upload.js +2 -1
- package/dist/commands/fork-subagent.js +2 -4
- package/dist/commands/fork.js +4 -4
- package/dist/commands/last-sessions.js +17 -10
- package/dist/commands/login.js +6 -0
- package/dist/commands/merge-worktree.js +47 -14
- package/dist/commands/model-variant.js +8 -1
- package/dist/commands/model.js +16 -9
- package/dist/commands/multioauth.js +1 -0
- package/dist/commands/new-worktree.js +170 -102
- package/dist/commands/queue.js +3 -2
- package/dist/commands/resume.js +4 -4
- package/dist/commands/session.js +78 -63
- package/dist/commands/user-command.js +6 -8
- package/dist/commands/worktrees.js +5 -13
- package/dist/config.js +16 -1
- package/dist/cpu-profiler.js +211 -0
- package/dist/cpu-profiler.test.js +160 -0
- package/dist/database.js +152 -3
- package/dist/db.js +33 -0
- package/dist/db.test.js +267 -1
- package/dist/default-channel-provisioning.test.js +148 -0
- package/dist/discord-bot.js +517 -488
- package/dist/discord-command-registration.js +25 -10
- package/dist/discord-command-registration.test.js +17 -0
- package/dist/discord-utils.js +97 -5
- package/dist/discord-utils.test.js +151 -1
- package/dist/errors.js +10 -0
- package/dist/exec-async.js +17 -5
- package/dist/external-directory-permissions.e2e.test.js +11 -7
- package/dist/external-directory-project-deny.e2e.test.js +5 -3
- package/dist/external-opencode-sync.js +94 -17
- package/dist/external-opencode-sync.test.js +206 -0
- package/dist/extract-sdk-error-message.test.js +13 -0
- package/dist/file-edit-log.js +197 -0
- package/dist/file-edit-log.test.js +258 -0
- package/dist/format-tables.js +52 -0
- package/dist/format-tables.test.js +93 -0
- package/dist/gateway-proxy-reconnect.e2e.test.js +28 -8
- package/dist/gateway-proxy.e2e.test.js +18 -18
- package/dist/git-worktree-core.js +182 -17
- package/dist/interaction-handler.js +416 -398
- package/dist/ipc-tools-plugin.js +68 -3
- package/dist/kimaki-opencode-plugin-loading.e2e.test.js +122 -58
- package/dist/kimaki-opencode-plugin.js +9 -3
- package/dist/kimaki-workspace-adaptor.js +33 -10
- package/dist/logger.js +11 -1
- package/dist/markdown.js +3 -1
- package/dist/markdown.test.js +11 -3
- package/dist/message-formatting.js +181 -51
- package/dist/message-formatting.test.js +529 -22
- package/dist/message-preprocessing.js +105 -1
- package/dist/oauth-rotation-shared.js +10 -0
- package/dist/openai-auth-plugin.js +1 -0
- package/dist/openai-auth-state.js +1 -0
- package/dist/opencode-command.js +28 -0
- package/dist/opencode-command.test.js +109 -1
- package/dist/opencode-interrupt-plugin.js +4 -0
- package/dist/opencode-interrupt-plugin.test.js +24 -0
- package/dist/opencode.js +82 -15
- package/dist/project-add.test.js +62 -0
- package/dist/question-abort-clears-dropdown.e2e.test.js +89 -0
- package/dist/question-abort-retry-text-before-dropdown.e2e.test.js +150 -0
- package/dist/question-text-before-dropdown.e2e.test.js +52 -0
- package/dist/queue-advanced-abort.e2e.test.js +8 -10
- package/dist/queue-advanced-action-buttons.e2e.test.js +8 -8
- package/dist/queue-advanced-e2e-setup.js +78 -5
- package/dist/queue-advanced-footer.e2e.test.js +60 -269
- package/dist/queue-advanced-model-switch.e2e.test.js +9 -11
- package/dist/queue-advanced-permissions-typing.e2e.test.js +15 -15
- package/dist/queue-advanced-question.e2e.test.js +32 -27
- package/dist/queue-advanced-typing-interrupt.e2e.test.js +10 -12
- package/dist/queue-advanced-typing.e2e.test.js +7 -9
- package/dist/queue-delete-message.e2e.test.js +3 -3
- package/dist/queue-drain-after-interactive-ui.e2e.test.js +5 -5
- package/dist/queue-interrupt-drain.e2e.test.js +122 -12
- package/dist/queue-question-select-drain.e2e.test.js +34 -132
- package/dist/runtime-lifecycle.e2e.test.js +21 -51
- package/dist/schema.js +35 -1
- package/dist/session-handler/event-stream-state.js +391 -50
- package/dist/session-handler/event-stream-state.test.js +736 -8
- package/dist/session-handler/model-utils.js +172 -5
- package/dist/session-handler/model-utils.test.js +445 -0
- package/dist/session-handler/opencode-session-event-log.js +30 -1
- package/dist/session-handler/thread-ingress.test.js +41 -0
- package/dist/session-handler/thread-runtime-state.js +2 -0
- package/dist/session-handler/thread-session-runtime-summary.test.js +121 -0
- package/dist/session-handler/thread-session-runtime.js +574 -299
- package/dist/session-search.js +123 -4
- package/dist/session-search.test.js +248 -1
- package/dist/session-sleep.test.js +224 -0
- package/dist/session-title-rename.test.js +37 -39
- package/dist/sleep.e2e.test.js +494 -0
- package/dist/startup-time.e2e.test.js +2 -1
- package/dist/store.js +3 -0
- package/dist/system-message.js +102 -16
- package/dist/system-message.test.js +192 -15
- package/dist/task-runner.js +96 -5
- package/dist/task-schedule.js +87 -1
- package/dist/task-schedule.test.js +149 -1
- package/dist/test-utils.js +17 -9
- package/dist/thinking-utils.js +10 -0
- package/dist/thinking-utils.test.js +35 -0
- package/dist/thread-message-queue.e2e.test.js +65 -213
- package/dist/undo-redo.e2e.test.js +17 -9
- package/dist/voice-attachment.js +23 -1
- package/dist/voice-handler.js +21 -4
- package/dist/voice-message.e2e.test.js +144 -30
- package/dist/voice-routing-input.e2e.test.js +195 -0
- package/dist/voice-routing-question.e2e.test.js +125 -0
- package/dist/voice.js +28 -8
- package/dist/voice.test.js +96 -30
- package/dist/worktree-clone-isolation.e2e.test.js +188 -0
- package/dist/worktree-lifecycle.e2e.test.js +55 -36
- package/dist/worktrees.js +226 -443
- package/dist/worktrees.test.js +435 -58
- package/dist/xai-auth-plugin.js +1 -0
- package/dist/xai-auth-state.js +1 -0
- package/package.json +8 -6
- package/skills/bump-holocron-consumers/SKILL.md +137 -0
- package/skills/critique/SKILL.md +4 -4
- package/skills/errore/SKILL.md +60 -218
- package/skills/goke/SKILL.md +150 -10
- package/skills/holocron/SKILL.md +154 -17
- package/skills/npm-package/SKILL.md +55 -35
- package/skills/profano/SKILL.md +17 -2
- package/skills/spiceflow/SKILL.md +39 -3
- package/skills/strada/SKILL.md +39 -0
- package/skills/zele/SKILL.md +19 -0
- package/src/agent-model.e2e.test.ts +287 -186
- package/src/anthropic-account-identity.ts +20 -1
- package/src/anthropic-auth-plugin.test.ts +19 -0
- package/src/anthropic-auth-plugin.ts +12 -16
- package/src/anthropic-auth-state.ts +1 -0
- package/src/bash-tool-schema-plugin.test.ts +55 -0
- package/src/bash-tool-schema-plugin.ts +46 -0
- package/src/bin.ts +78 -26
- package/src/cli-commands/maintenance.ts +59 -13
- package/src/cli-commands/project.ts +14 -35
- package/src/cli-commands/send.ts +31 -1
- package/src/cli-commands/session.ts +374 -114
- package/src/cli-commands/task.ts +34 -15
- package/src/cli-parsing.test.ts +103 -4
- package/src/cli-runner.test.ts +49 -0
- package/src/cli-runner.ts +44 -7
- package/src/cli-send-thread.e2e.test.ts +9 -11
- package/src/cli.ts +53 -0
- package/src/commands/abort.ts +6 -1
- package/src/commands/action-buttons.ts +2 -1
- package/src/commands/add-project.ts +4 -8
- package/src/commands/agent.ts +205 -29
- package/src/commands/ask-question.test.ts +48 -1
- package/src/commands/ask-question.ts +23 -33
- package/src/commands/btw.ts +40 -5
- package/src/commands/file-upload.ts +2 -1
- package/src/commands/fork-subagent.ts +2 -3
- package/src/commands/fork.ts +4 -3
- package/src/commands/last-sessions.ts +10 -4
- package/src/commands/login.ts +6 -0
- package/src/commands/merge-worktree.ts +54 -16
- package/src/commands/model-variant.ts +9 -1
- package/src/commands/model.ts +20 -9
- package/src/commands/multioauth.ts +1 -0
- package/src/commands/new-worktree.ts +138 -55
- package/src/commands/queue.ts +3 -2
- package/src/commands/resume.ts +4 -3
- package/src/commands/session.ts +94 -70
- package/src/commands/user-command.ts +6 -6
- package/src/commands/worktrees.ts +5 -12
- package/src/config.ts +18 -1
- package/src/cpu-profiler.test.ts +183 -0
- package/src/cpu-profiler.ts +293 -0
- package/src/database.ts +235 -3
- package/src/db.test.ts +293 -0
- package/src/db.ts +38 -0
- package/src/default-channel-provisioning.test.ts +155 -0
- package/src/discord-bot.ts +78 -36
- package/src/discord-command-registration.test.ts +20 -0
- package/src/discord-command-registration.ts +42 -12
- package/src/discord-utils.test.ts +176 -0
- package/src/discord-utils.ts +152 -7
- package/src/errors.ts +12 -0
- package/src/exec-async.ts +20 -11
- package/src/external-directory-permissions.e2e.test.ts +11 -7
- package/src/external-directory-project-deny.e2e.test.ts +5 -3
- package/src/external-opencode-sync.test.ts +248 -0
- package/src/external-opencode-sync.ts +122 -19
- package/src/extract-sdk-error-message.test.ts +18 -0
- package/src/file-edit-log.test.ts +287 -0
- package/src/file-edit-log.ts +283 -0
- package/src/format-tables.test.ts +101 -0
- package/src/format-tables.ts +75 -0
- package/src/gateway-proxy-reconnect.e2e.test.ts +38 -8
- package/src/gateway-proxy.e2e.test.ts +18 -17
- package/src/git-worktree-core.ts +247 -20
- package/src/interaction-handler.ts +36 -13
- package/src/ipc-tools-plugin.ts +82 -5
- package/src/kimaki-opencode-plugin-loading.e2e.test.ts +188 -97
- package/src/kimaki-opencode-plugin.ts +9 -3
- package/src/kimaki-workspace-adaptor.ts +40 -11
- package/src/logger.ts +14 -4
- package/src/markdown.test.ts +13 -3
- package/src/markdown.ts +4 -1
- package/src/message-formatting.test.ts +672 -25
- package/src/message-formatting.ts +242 -53
- package/src/message-preprocessing.ts +116 -1
- package/src/oauth-rotation-shared.ts +10 -0
- package/src/openai-auth-plugin.ts +1 -0
- package/src/openai-auth-state.ts +1 -0
- package/src/opencode-command.test.ts +140 -0
- package/src/opencode-command.ts +38 -0
- package/src/opencode-interrupt-plugin.test.ts +32 -0
- package/src/opencode-interrupt-plugin.ts +3 -0
- package/src/opencode.ts +115 -15
- package/src/project-add.test.ts +80 -0
- package/src/question-abort-clears-dropdown.e2e.test.ts +109 -0
- package/src/question-abort-retry-text-before-dropdown.e2e.test.ts +172 -0
- package/src/question-text-before-dropdown.e2e.test.ts +67 -0
- package/src/queue-advanced-abort.e2e.test.ts +8 -9
- package/src/queue-advanced-action-buttons.e2e.test.ts +8 -8
- package/src/queue-advanced-e2e-setup.ts +83 -4
- package/src/queue-advanced-footer.e2e.test.ts +67 -315
- package/src/queue-advanced-model-switch.e2e.test.ts +9 -10
- package/src/queue-advanced-permissions-typing.e2e.test.ts +15 -15
- package/src/queue-advanced-question.e2e.test.ts +36 -30
- package/src/queue-advanced-typing-interrupt.e2e.test.ts +11 -11
- package/src/queue-advanced-typing.e2e.test.ts +8 -8
- package/src/queue-delete-message.e2e.test.ts +3 -3
- package/src/queue-drain-after-interactive-ui.e2e.test.ts +5 -5
- package/src/queue-interrupt-drain.e2e.test.ts +133 -12
- package/src/queue-question-select-drain.e2e.test.ts +36 -161
- package/src/runtime-lifecycle.e2e.test.ts +21 -54
- package/src/schema.sql +15 -2
- package/src/schema.ts +37 -1
- package/src/session-handler/event-stream-state.test.ts +790 -7
- package/src/session-handler/event-stream-state.ts +568 -53
- package/src/session-handler/model-utils.test.ts +537 -0
- package/src/session-handler/model-utils.ts +281 -5
- package/src/session-handler/opencode-session-event-log.ts +31 -1
- package/src/session-handler/thread-ingress.test.ts +54 -0
- package/src/session-handler/thread-runtime-state.ts +3 -0
- package/src/session-handler/thread-session-runtime-summary.test.ts +169 -0
- package/src/session-handler/thread-session-runtime.ts +712 -358
- package/src/session-search.test.ts +281 -0
- package/src/session-search.ts +241 -7
- package/src/session-sleep.test.ts +294 -0
- package/src/session-title-rename.test.ts +54 -48
- package/src/sleep.e2e.test.ts +565 -0
- package/src/startup-time.e2e.test.ts +5 -1
- package/src/store.ts +22 -0
- package/src/system-message.test.ts +226 -15
- package/src/system-message.ts +130 -16
- package/src/task-runner.ts +138 -3
- package/src/task-schedule.test.ts +170 -0
- package/src/task-schedule.ts +141 -1
- package/src/test-utils.ts +21 -10
- package/src/thinking-utils.test.ts +46 -0
- package/src/thinking-utils.ts +21 -0
- package/src/thread-message-queue.e2e.test.ts +65 -245
- package/src/undo-redo.e2e.test.ts +17 -9
- package/src/voice-attachment.ts +29 -1
- package/src/voice-handler.ts +23 -4
- package/src/voice-message.e2e.test.ts +149 -29
- package/src/voice-routing-input.e2e.test.ts +203 -0
- package/src/voice-routing-question.e2e.test.ts +131 -0
- package/src/voice.test.ts +107 -29
- package/src/voice.ts +34 -6
- package/src/worktree-clone-isolation.e2e.test.ts +213 -0
- package/src/worktree-lifecycle.e2e.test.ts +55 -35
- package/src/worktrees.test.ts +505 -64
- package/src/worktrees.ts +257 -603
- package/src/xai-auth-plugin.ts +1 -0
- package/src/xai-auth-state.ts +1 -0
- package/dist/message-finish-field.e2e.test.js +0 -169
- package/dist/subagent-rate-limit-plugin.js +0 -184
- package/src/message-finish-field.e2e.test.ts +0 -196
- package/src/subagent-rate-limit-plugin.ts +0 -228
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Session inspection and archival terminal commands.
|
|
2
2
|
import { goke } from 'goke';
|
|
3
3
|
import { z } from 'zod';
|
|
4
|
+
import dedent from 'string-dedent';
|
|
4
5
|
import { note } from '@clack/prompts';
|
|
5
6
|
import YAML from 'yaml';
|
|
6
7
|
import * as errore from 'errore';
|
|
@@ -11,14 +12,17 @@ import { fileURLToPath } from 'node:url';
|
|
|
11
12
|
import { spawn, execSync } from 'node:child_process';
|
|
12
13
|
import { createLogger, LogPrefix, initLogFile } from '../logger.js';
|
|
13
14
|
import { createDiscordClient, initDatabase, getChannelDirectory, initializeOpencodeForDirectory, createProjectChannels } from '../discord-bot.js';
|
|
14
|
-
import { getBotTokenWithMode, getThreadSession, getThreadIdBySessionId, getSessionEventSnapshot, getDb, createScheduledTask, listScheduledTasks, cancelScheduledTask, getScheduledTask, updateScheduledTask, getSessionStartSourcesBySessionIds, deleteChannelDirectoryById, findChannelsByDirectory, getThreadWorktreeOrWorkspace } from '../database.js';
|
|
15
|
+
import { getBotTokenWithMode, getThreadSession, getThreadIdBySessionId, getSessionEventSnapshot, getDb, createScheduledTask, listScheduledTasks, cancelScheduledTask, getScheduledTask, updateScheduledTask, getSessionStartSourcesBySessionIds, deleteChannelDirectoryById, findChannelsByDirectory, getThreadWorktreeOrWorkspace, getAllTextChannelDirectories } from '../database.js';
|
|
15
16
|
import { ShareMarkdown } from '../markdown.js';
|
|
16
|
-
import { parseSessionSearchPattern,
|
|
17
|
+
import { parseSessionSearchPattern, collectSessionSearchMatches, validateSessionSearchScope, resolveSessionSearchDirectories, parseSessionSearchDays, sessionSearchMinUpdated, SESSION_SEARCH_DEFAULT_DAYS } from '../session-search.js';
|
|
17
18
|
import { formatWorktreeName, formatAutoWorktreeName } from '../commands/new-worktree.js';
|
|
19
|
+
import { formatTimeAgo } from '../commands/worktrees.js';
|
|
20
|
+
import { editorsForFile, loadFileEditEvents } from '../file-edit-log.js';
|
|
18
21
|
import { WORKTREE_PREFIX } from '../commands/merge-worktree.js';
|
|
19
22
|
import { buildOpencodeEventLogLine } from '../session-handler/opencode-session-event-log.js';
|
|
20
23
|
import { createDiscordRest } from '../discord-urls.js';
|
|
21
24
|
import { archiveThread, uploadFilesToDiscord, stripMentions } from '../discord-utils.js';
|
|
25
|
+
import { OpenCodeSdkError } from '../errors.js';
|
|
22
26
|
import { setDataDir, setProjectsDir, getDataDir, getProjectsDir } from '../config.js';
|
|
23
27
|
import { execAsync, validateWorktreeDirectory } from '../worktrees.js';
|
|
24
28
|
import { upgrade, getCurrentVersion } from '../upgrade.js';
|
|
@@ -53,6 +57,8 @@ async function resolveSessionDirectoryFromDatabase({ sessionId, }) {
|
|
|
53
57
|
cli
|
|
54
58
|
.command('session list', 'List all OpenCode sessions, marking which were started via Kimaki')
|
|
55
59
|
.option('--project <path>', 'Project directory to list sessions for (defaults to cwd)')
|
|
60
|
+
.option('--active', 'Only list active sessions; exits 1 when none remain')
|
|
61
|
+
.option('--exclude <sessionId>', 'Exclude one session ID from the results')
|
|
56
62
|
.option('--json', 'Output as JSON')
|
|
57
63
|
.action(async (options) => {
|
|
58
64
|
try {
|
|
@@ -66,9 +72,32 @@ cli
|
|
|
66
72
|
}
|
|
67
73
|
const sessionsResponse = await getClient().session.list();
|
|
68
74
|
const sessions = sessionsResponse.data || [];
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
75
|
+
const statuses = await (async () => {
|
|
76
|
+
if (!options.active)
|
|
77
|
+
return null;
|
|
78
|
+
const response = await getClient().session.status({
|
|
79
|
+
directory: projectDirectory,
|
|
80
|
+
});
|
|
81
|
+
if (response.error) {
|
|
82
|
+
cliLogger.error('Failed to list active sessions');
|
|
83
|
+
process.exit(EXIT_NO_RESTART);
|
|
84
|
+
}
|
|
85
|
+
return response.data || {};
|
|
86
|
+
})();
|
|
87
|
+
const selectedSessions = sessions.filter((session) => {
|
|
88
|
+
if (session.id === options.exclude)
|
|
89
|
+
return false;
|
|
90
|
+
if (!options.active)
|
|
91
|
+
return true;
|
|
92
|
+
const status = statuses?.[session.id];
|
|
93
|
+
return Boolean(status && status.type !== 'idle');
|
|
94
|
+
});
|
|
95
|
+
if (selectedSessions.length === 0) {
|
|
96
|
+
if (options.json)
|
|
97
|
+
console.log('[]');
|
|
98
|
+
else
|
|
99
|
+
cliLogger.log(options.active ? 'No active sessions found' : 'No sessions found');
|
|
100
|
+
process.exit(options.active ? 1 : 0);
|
|
72
101
|
}
|
|
73
102
|
// Look up which sessions were started via kimaki (have a thread mapping)
|
|
74
103
|
const db = await getDb();
|
|
@@ -78,7 +107,7 @@ cli
|
|
|
78
107
|
const sessionToThread = new Map(threadSessions
|
|
79
108
|
.filter((row) => row.session_id !== '')
|
|
80
109
|
.map((row) => [row.session_id, row.thread_id]));
|
|
81
|
-
const sessionStartSources = await getSessionStartSourcesBySessionIds(
|
|
110
|
+
const sessionStartSources = await getSessionStartSourcesBySessionIds(selectedSessions.map((session) => session.id));
|
|
82
111
|
const scheduleModeLabel = ({ scheduleKind, }) => {
|
|
83
112
|
if (scheduleKind === 'at') {
|
|
84
113
|
return 'delay';
|
|
@@ -86,7 +115,7 @@ cli
|
|
|
86
115
|
return 'cron';
|
|
87
116
|
};
|
|
88
117
|
if (options.json) {
|
|
89
|
-
const output =
|
|
118
|
+
const output = selectedSessions.map((session) => {
|
|
90
119
|
const startSource = sessionStartSources.get(session.id);
|
|
91
120
|
const startedBy = startSource
|
|
92
121
|
? `scheduled-${scheduleModeLabel({ scheduleKind: startSource.schedule_kind })}`
|
|
@@ -100,12 +129,13 @@ cli
|
|
|
100
129
|
threadId: sessionToThread.get(session.id) || null,
|
|
101
130
|
startedBy,
|
|
102
131
|
scheduledTaskId: startSource?.scheduled_task_id || null,
|
|
132
|
+
status: options.active ? statuses?.[session.id]?.type || 'busy' : undefined,
|
|
103
133
|
};
|
|
104
134
|
});
|
|
105
135
|
console.log(JSON.stringify(output, null, 2));
|
|
106
136
|
process.exit(0);
|
|
107
137
|
}
|
|
108
|
-
for (const session of
|
|
138
|
+
for (const session of selectedSessions) {
|
|
109
139
|
const threadId = sessionToThread.get(session.id);
|
|
110
140
|
const startSource = sessionStartSources.get(session.id);
|
|
111
141
|
const source = threadId ? '(kimaki)' : '(opencode)';
|
|
@@ -114,7 +144,10 @@ cli
|
|
|
114
144
|
: '';
|
|
115
145
|
const updatedAt = new Date(session.time.updated).toISOString();
|
|
116
146
|
const threadInfo = threadId ? ` | thread: ${threadId}` : '';
|
|
117
|
-
|
|
147
|
+
const statusInfo = options.active
|
|
148
|
+
? ` | status: ${statuses?.[session.id]?.type || 'busy'}`
|
|
149
|
+
: '';
|
|
150
|
+
console.log(`${session.id} | ${session.title || 'Untitled Session'} | ${session.directory} | ${updatedAt} | ${source}${threadInfo}${startedBy}${statusInfo}`);
|
|
118
151
|
}
|
|
119
152
|
process.exit(0);
|
|
120
153
|
}
|
|
@@ -123,6 +156,79 @@ cli
|
|
|
123
156
|
process.exit(EXIT_NO_RESTART);
|
|
124
157
|
}
|
|
125
158
|
});
|
|
159
|
+
cli
|
|
160
|
+
.command('session editors <file>', dedent `
|
|
161
|
+
List sessions that last edited a file, newest first.
|
|
162
|
+
|
|
163
|
+
Use this before a commit in another session so the \`Session:\` line
|
|
164
|
+
uses the session that actually edited the file.
|
|
165
|
+
`)
|
|
166
|
+
.option('--json', 'Output as JSON')
|
|
167
|
+
.option('--limit <n>', z.number().default(20).describe('Max sessions to show'))
|
|
168
|
+
.example('kimaki session editors src/cli.ts')
|
|
169
|
+
.example('kimaki session editors src/cli.ts --json')
|
|
170
|
+
.action(async (file, options, { console, process }) => {
|
|
171
|
+
try {
|
|
172
|
+
const cwd = process.cwd;
|
|
173
|
+
const loaded = loadFileEditEvents({ dataDir: getDataDir() });
|
|
174
|
+
if (loaded instanceof Error) {
|
|
175
|
+
console.error(loaded.message);
|
|
176
|
+
process.exit(EXIT_NO_RESTART);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const editors = editorsForFile({
|
|
180
|
+
events: loaded,
|
|
181
|
+
filePath: file,
|
|
182
|
+
cwd,
|
|
183
|
+
}).slice(0, options.limit);
|
|
184
|
+
if (editors.length === 0) {
|
|
185
|
+
console.error(`No recorded editors for ${path.resolve(cwd, file)}`);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const titles = new Map();
|
|
190
|
+
try {
|
|
191
|
+
await initDatabase();
|
|
192
|
+
const db = await getDb();
|
|
193
|
+
const sessionRows = await db.query.thread_sessions.findMany({
|
|
194
|
+
columns: { session_id: true, last_synced_name: true },
|
|
195
|
+
where: { session_id: { in: editors.map((editor) => editor.sessionId) } },
|
|
196
|
+
orderBy: { updated_at: 'desc' },
|
|
197
|
+
});
|
|
198
|
+
for (const row of sessionRows) {
|
|
199
|
+
if (!titles.has(row.session_id) && row.last_synced_name) {
|
|
200
|
+
titles.set(row.session_id, row.last_synced_name);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
206
|
+
}
|
|
207
|
+
const rows = editors.map((editor) => {
|
|
208
|
+
const title = titles.get(editor.sessionId) || '-';
|
|
209
|
+
const editedAt = new Date(editor.at);
|
|
210
|
+
return {
|
|
211
|
+
sessionId: editor.sessionId,
|
|
212
|
+
title,
|
|
213
|
+
editedAt: editedAt.toISOString(),
|
|
214
|
+
ago: formatTimeAgo(editedAt),
|
|
215
|
+
};
|
|
216
|
+
});
|
|
217
|
+
if (options.json) {
|
|
218
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
219
|
+
process.exit(0);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
for (const row of rows) {
|
|
223
|
+
console.log(`${row.sessionId} | ${row.title} | ${row.ago}`);
|
|
224
|
+
}
|
|
225
|
+
process.exit(0);
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
console.error(error instanceof Error ? error.stack : String(error));
|
|
229
|
+
process.exit(EXIT_NO_RESTART);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
126
232
|
cli
|
|
127
233
|
.command('session read <sessionId>', 'Read a session conversation as markdown (pipe to file to grep)')
|
|
128
234
|
.option('--project <path>', 'Project directory (defaults to cwd)')
|
|
@@ -213,16 +319,26 @@ cli
|
|
|
213
319
|
}
|
|
214
320
|
});
|
|
215
321
|
cli
|
|
216
|
-
.command('session search <query>',
|
|
322
|
+
.command('session search <query>', `Search past sessions for text or /regex/flags. Defaults to the last ${SESSION_SEARCH_DEFAULT_DAYS} days; use --days 0 for all time. Add --all for every locally registered project.`)
|
|
217
323
|
.option('--project <path>', 'Project directory (defaults to cwd)')
|
|
218
324
|
.option('--channel <channelId>', 'Resolve project from a Discord channel ID')
|
|
325
|
+
.option('--all', 'Search every locally registered project')
|
|
326
|
+
.option('--days <n>', `Only search sessions updated in the last n days (default: ${SESSION_SEARCH_DEFAULT_DAYS}; 0 = all time)`)
|
|
219
327
|
.option('--limit <n>', 'Maximum matched sessions to return (default: 20)')
|
|
220
328
|
.option('--json', 'Output as JSON')
|
|
329
|
+
.example('kimaki session search "auth timeout"')
|
|
330
|
+
.example('kimaki session search "auth timeout" --days 0')
|
|
331
|
+
.example('kimaki session search "auth timeout" --all')
|
|
221
332
|
.action(async (query, options) => {
|
|
222
333
|
try {
|
|
223
334
|
await initDatabase();
|
|
224
|
-
|
|
225
|
-
|
|
335
|
+
const scopeError = validateSessionSearchScope({
|
|
336
|
+
all: options.all,
|
|
337
|
+
project: options.project,
|
|
338
|
+
channel: options.channel,
|
|
339
|
+
});
|
|
340
|
+
if (scopeError) {
|
|
341
|
+
cliLogger.error(scopeError.message);
|
|
226
342
|
process.exit(EXIT_NO_RESTART);
|
|
227
343
|
}
|
|
228
344
|
const limit = (() => {
|
|
@@ -237,7 +353,16 @@ cli
|
|
|
237
353
|
cliLogger.error(limit.message);
|
|
238
354
|
process.exit(EXIT_NO_RESTART);
|
|
239
355
|
}
|
|
240
|
-
const
|
|
356
|
+
const days = parseSessionSearchDays(typeof options.days === 'string' ? options.days : undefined);
|
|
357
|
+
if (days instanceof Error) {
|
|
358
|
+
cliLogger.error(days.message);
|
|
359
|
+
process.exit(EXIT_NO_RESTART);
|
|
360
|
+
}
|
|
361
|
+
const minUpdated = sessionSearchMinUpdated({ days });
|
|
362
|
+
const explicitDirectory = await (async () => {
|
|
363
|
+
if (options.all) {
|
|
364
|
+
return undefined;
|
|
365
|
+
}
|
|
241
366
|
if (options.channel) {
|
|
242
367
|
const channelConfig = await getChannelDirectory(options.channel);
|
|
243
368
|
if (!channelConfig) {
|
|
@@ -245,15 +370,45 @@ cli
|
|
|
245
370
|
}
|
|
246
371
|
return path.resolve(channelConfig.directory);
|
|
247
372
|
}
|
|
248
|
-
|
|
373
|
+
if (options.project) {
|
|
374
|
+
return path.resolve(options.project);
|
|
375
|
+
}
|
|
376
|
+
return undefined;
|
|
249
377
|
})();
|
|
250
|
-
if (
|
|
251
|
-
cliLogger.error(
|
|
378
|
+
if (explicitDirectory instanceof Error) {
|
|
379
|
+
cliLogger.error(explicitDirectory.message);
|
|
252
380
|
process.exit(EXIT_NO_RESTART);
|
|
253
381
|
}
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
382
|
+
const registeredDirectories = options.all
|
|
383
|
+
? (await getAllTextChannelDirectories()).map((directory) => {
|
|
384
|
+
return path.resolve(directory);
|
|
385
|
+
})
|
|
386
|
+
: [];
|
|
387
|
+
const projectDirectories = resolveSessionSearchDirectories({
|
|
388
|
+
all: Boolean(options.all),
|
|
389
|
+
registeredDirectories,
|
|
390
|
+
cwd: path.resolve('.'),
|
|
391
|
+
explicitDirectory,
|
|
392
|
+
});
|
|
393
|
+
if (projectDirectories instanceof Error) {
|
|
394
|
+
cliLogger.error(projectDirectories.message);
|
|
395
|
+
process.exit(EXIT_NO_RESTART);
|
|
396
|
+
}
|
|
397
|
+
const existingDirectories = [];
|
|
398
|
+
for (const directory of projectDirectories) {
|
|
399
|
+
if (fs.existsSync(directory)) {
|
|
400
|
+
existingDirectories.push(directory);
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
if (options.all) {
|
|
404
|
+
cliLogger.warn(`Skipping missing directory: ${directory}`);
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
cliLogger.error(`Directory does not exist: ${directory}`);
|
|
408
|
+
process.exit(EXIT_NO_RESTART);
|
|
409
|
+
}
|
|
410
|
+
if (existingDirectories.length === 0) {
|
|
411
|
+
cliLogger.error('No searchable project directories found. Add a project with `kimaki project add`, or pass --project.');
|
|
257
412
|
process.exit(EXIT_NO_RESTART);
|
|
258
413
|
}
|
|
259
414
|
const searchPattern = parseSessionSearchPattern(query);
|
|
@@ -261,15 +416,43 @@ cli
|
|
|
261
416
|
cliLogger.error(searchPattern.message);
|
|
262
417
|
process.exit(EXIT_NO_RESTART);
|
|
263
418
|
}
|
|
264
|
-
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
419
|
+
const clientsBySessionId = new Map();
|
|
420
|
+
const searchedDirectories = [];
|
|
421
|
+
const searchableSessions = [];
|
|
422
|
+
const listedDirectories = await Promise.all(existingDirectories.map(async (projectDirectory) => {
|
|
423
|
+
cliLogger.log(`Connecting to OpenCode server for ${projectDirectory}...`);
|
|
424
|
+
const getClient = await initializeOpencodeForDirectory(projectDirectory);
|
|
425
|
+
if (getClient instanceof Error) {
|
|
426
|
+
return { projectDirectory, getClient, sessions: [] };
|
|
427
|
+
}
|
|
428
|
+
const sessionsResponse = await getClient().session.list();
|
|
429
|
+
return {
|
|
430
|
+
projectDirectory,
|
|
431
|
+
getClient,
|
|
432
|
+
sessions: sessionsResponse.data || [],
|
|
433
|
+
};
|
|
434
|
+
}));
|
|
435
|
+
for (const listed of listedDirectories) {
|
|
436
|
+
if (listed.getClient instanceof Error) {
|
|
437
|
+
if (options.all) {
|
|
438
|
+
cliLogger.warn(`Skipping ${listed.projectDirectory}: failed to connect to OpenCode: ${listed.getClient.message}`);
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
cliLogger.error('Failed to connect to OpenCode:', listed.getClient.message);
|
|
442
|
+
process.exit(EXIT_NO_RESTART);
|
|
443
|
+
}
|
|
444
|
+
searchedDirectories.push(listed.projectDirectory);
|
|
445
|
+
for (const session of listed.sessions) {
|
|
446
|
+
clientsBySessionId.set(session.id, listed.getClient);
|
|
447
|
+
searchableSessions.push({
|
|
448
|
+
id: session.id,
|
|
449
|
+
title: session.title || 'Untitled Session',
|
|
450
|
+
directory: session.directory || listed.projectDirectory,
|
|
451
|
+
updated: session.time.updated,
|
|
452
|
+
});
|
|
453
|
+
}
|
|
269
454
|
}
|
|
270
|
-
|
|
271
|
-
const sessions = sessionsResponse.data || [];
|
|
272
|
-
if (sessions.length === 0) {
|
|
455
|
+
if (searchableSessions.length === 0) {
|
|
273
456
|
cliLogger.log('No sessions found');
|
|
274
457
|
process.exit(0);
|
|
275
458
|
}
|
|
@@ -280,82 +463,62 @@ cli
|
|
|
280
463
|
const sessionToThread = new Map(threadSessions
|
|
281
464
|
.filter((row) => row.session_id !== '')
|
|
282
465
|
.map((row) => [row.session_id, row.thread_id]));
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
466
|
+
const scopeLabel = options.all
|
|
467
|
+
? `${searchedDirectories.length} project(s)`
|
|
468
|
+
: searchedDirectories[0] || path.resolve('.');
|
|
469
|
+
const daysLabel = days === 0 ? 'all time' : `the last ${days} day${days === 1 ? '' : 's'}`;
|
|
470
|
+
const printMatch = (match) => {
|
|
471
|
+
const threadInfo = match.threadId ? ` | thread: ${match.threadId}` : '';
|
|
472
|
+
console.log(`${match.id} | ${match.title} | ${match.updated} | ${match.source}${threadInfo}`);
|
|
473
|
+
console.log(` Directory: ${match.directory}`);
|
|
474
|
+
match.snippets.forEach((snippet) => {
|
|
475
|
+
console.log(` - ${snippet}`);
|
|
292
476
|
});
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
477
|
+
};
|
|
478
|
+
let printedHeader = false;
|
|
479
|
+
const { matches: matchedSessions, scannedSessions } = await collectSessionSearchMatches({
|
|
480
|
+
sessions: searchableSessions,
|
|
481
|
+
searchPattern,
|
|
482
|
+
sessionToThread,
|
|
483
|
+
limit,
|
|
484
|
+
minUpdated,
|
|
485
|
+
onMatch: options.json
|
|
486
|
+
? undefined
|
|
487
|
+
: (match) => {
|
|
488
|
+
if (!printedHeader) {
|
|
489
|
+
printedHeader = true;
|
|
490
|
+
cliLogger.log(`Found matching session(s) for ${searchPattern.raw} in ${scopeLabel} (${daysLabel})`);
|
|
491
|
+
}
|
|
492
|
+
printMatch(match);
|
|
493
|
+
},
|
|
494
|
+
loadMessages: async (session) => {
|
|
495
|
+
const getClient = clientsBySessionId.get(session.id);
|
|
496
|
+
if (!getClient) {
|
|
497
|
+
return [];
|
|
498
|
+
}
|
|
499
|
+
const messagesResponse = await getClient().session.messages({
|
|
500
|
+
sessionID: session.id,
|
|
316
501
|
});
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
continue;
|
|
321
|
-
}
|
|
322
|
-
const threadId = sessionToThread.get(session.id);
|
|
323
|
-
matchedSessions.push({
|
|
324
|
-
id: session.id,
|
|
325
|
-
title: session.title || 'Untitled Session',
|
|
326
|
-
directory: session.directory,
|
|
327
|
-
updated: new Date(session.time.updated).toISOString(),
|
|
328
|
-
source: threadId ? 'kimaki' : 'opencode',
|
|
329
|
-
threadId: threadId || null,
|
|
330
|
-
snippets,
|
|
331
|
-
});
|
|
332
|
-
if (matchedSessions.length >= limit) {
|
|
333
|
-
break;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
502
|
+
return messagesResponse.data || [];
|
|
503
|
+
},
|
|
504
|
+
});
|
|
336
505
|
if (options.json) {
|
|
337
506
|
console.log(JSON.stringify({
|
|
338
507
|
query: searchPattern.raw,
|
|
339
508
|
mode: searchPattern.mode,
|
|
340
|
-
|
|
509
|
+
all: Boolean(options.all),
|
|
510
|
+
days,
|
|
511
|
+
projectDirectories: searchedDirectories,
|
|
341
512
|
scannedSessions,
|
|
342
513
|
matches: matchedSessions,
|
|
343
514
|
}, null, 2));
|
|
344
515
|
process.exit(0);
|
|
345
516
|
}
|
|
346
517
|
if (matchedSessions.length === 0) {
|
|
347
|
-
|
|
518
|
+
const cutoffHint = days === 0 ? '' : '. Use --days 0 to search all time';
|
|
519
|
+
cliLogger.log(`No matches found for ${searchPattern.raw} in ${scopeLabel} (${scannedSessions} sessions scanned, ${daysLabel})${cutoffHint}`);
|
|
348
520
|
process.exit(0);
|
|
349
521
|
}
|
|
350
|
-
cliLogger.log(`Found ${matchedSessions.length} matching session(s) for ${searchPattern.raw} in ${projectDirectory}`);
|
|
351
|
-
for (const match of matchedSessions) {
|
|
352
|
-
const threadInfo = match.threadId ? ` | thread: ${match.threadId}` : '';
|
|
353
|
-
console.log(`${match.id} | ${match.title} | ${match.updated} | ${match.source}${threadInfo}`);
|
|
354
|
-
console.log(` Directory: ${match.directory}`);
|
|
355
|
-
match.snippets.forEach((snippet) => {
|
|
356
|
-
console.log(` - ${snippet}`);
|
|
357
|
-
});
|
|
358
|
-
}
|
|
359
522
|
process.exit(0);
|
|
360
523
|
}
|
|
361
524
|
catch (error) {
|
|
@@ -430,7 +593,7 @@ cli
|
|
|
430
593
|
process.exit(0);
|
|
431
594
|
});
|
|
432
595
|
cli
|
|
433
|
-
.command('session archive [threadId]', 'Archive a Discord thread
|
|
596
|
+
.command('session archive [threadId]', 'Archive a Discord thread without stopping its mapped OpenCode session')
|
|
434
597
|
.option('--session <sessionId>', 'Resolve thread from an OpenCode session ID')
|
|
435
598
|
.action(async (threadIdArg, options) => {
|
|
436
599
|
try {
|
|
@@ -544,6 +707,75 @@ cli
|
|
|
544
707
|
process.exit(EXIT_NO_RESTART);
|
|
545
708
|
}
|
|
546
709
|
});
|
|
710
|
+
cli
|
|
711
|
+
.command('session title <title>', 'Update the OpenCode session title. Discord thread name follows automatically.')
|
|
712
|
+
.option('--session <sessionId>', 'OpenCode session ID')
|
|
713
|
+
.option('--thread <threadId>', 'Discord thread ID')
|
|
714
|
+
.example("kimaki session title 'Fix queue draining' --session ses_xxx")
|
|
715
|
+
.action(async (title, options) => {
|
|
716
|
+
try {
|
|
717
|
+
await initDatabase();
|
|
718
|
+
const trimmedTitle = title.trim();
|
|
719
|
+
if (!trimmedTitle) {
|
|
720
|
+
cliLogger.error('Title must not be empty');
|
|
721
|
+
process.exit(EXIT_NO_RESTART);
|
|
722
|
+
}
|
|
723
|
+
if (options.session && options.thread) {
|
|
724
|
+
cliLogger.error('Use either --session or --thread, not both');
|
|
725
|
+
process.exit(EXIT_NO_RESTART);
|
|
726
|
+
}
|
|
727
|
+
if (!options.session && !options.thread) {
|
|
728
|
+
cliLogger.error('Provide --session <sessionId> or --thread <threadId>');
|
|
729
|
+
process.exit(EXIT_NO_RESTART);
|
|
730
|
+
}
|
|
731
|
+
const sessionId = await (async () => {
|
|
732
|
+
if (options.session)
|
|
733
|
+
return options.session;
|
|
734
|
+
const threadId = options.thread;
|
|
735
|
+
if (!threadId)
|
|
736
|
+
return null;
|
|
737
|
+
return getThreadSession(threadId);
|
|
738
|
+
})();
|
|
739
|
+
if (!sessionId) {
|
|
740
|
+
cliLogger.error(options.thread
|
|
741
|
+
? `No OpenCode session found for thread: ${options.thread}`
|
|
742
|
+
: 'Provide --session <sessionId> or --thread <threadId>');
|
|
743
|
+
process.exit(EXIT_NO_RESTART);
|
|
744
|
+
}
|
|
745
|
+
const directory = await resolveSessionDirectoryFromDatabase({
|
|
746
|
+
sessionId,
|
|
747
|
+
});
|
|
748
|
+
if (directory instanceof Error) {
|
|
749
|
+
cliLogger.error(directory.message);
|
|
750
|
+
process.exit(EXIT_NO_RESTART);
|
|
751
|
+
}
|
|
752
|
+
const serverResult = await initializeOpencodeForDirectory(directory);
|
|
753
|
+
if (serverResult instanceof Error) {
|
|
754
|
+
cliLogger.error(`Failed to initialize OpenCode: ${serverResult.message}`);
|
|
755
|
+
process.exit(EXIT_NO_RESTART);
|
|
756
|
+
}
|
|
757
|
+
const updateResult = await serverResult()
|
|
758
|
+
.session.update({
|
|
759
|
+
sessionID: sessionId,
|
|
760
|
+
title: trimmedTitle,
|
|
761
|
+
})
|
|
762
|
+
.catch((e) => new OpenCodeSdkError({ operation: 'session.update', cause: e }));
|
|
763
|
+
if (updateResult instanceof Error) {
|
|
764
|
+
cliLogger.error(updateResult.message);
|
|
765
|
+
process.exit(EXIT_NO_RESTART);
|
|
766
|
+
}
|
|
767
|
+
if (updateResult.error) {
|
|
768
|
+
cliLogger.error('OpenCode rejected the session title update');
|
|
769
|
+
process.exit(EXIT_NO_RESTART);
|
|
770
|
+
}
|
|
771
|
+
note(`Updated OpenCode title: ${trimmedTitle}`, 'Title updated');
|
|
772
|
+
process.exit(0);
|
|
773
|
+
}
|
|
774
|
+
catch (error) {
|
|
775
|
+
cliLogger.error('Error:', error instanceof Error ? error.stack : String(error));
|
|
776
|
+
process.exit(EXIT_NO_RESTART);
|
|
777
|
+
}
|
|
778
|
+
});
|
|
547
779
|
cli
|
|
548
780
|
.command('session discord-url <sessionId>', 'Print the Discord thread URL for a session')
|
|
549
781
|
.option('--json', 'Output as JSON')
|
|
@@ -22,8 +22,9 @@ import { archiveThread, uploadFilesToDiscord, stripMentions } from '../discord-u
|
|
|
22
22
|
import { setDataDir, setProjectsDir, getDataDir, getProjectsDir } from '../config.js';
|
|
23
23
|
import { execAsync, validateWorktreeDirectory } from '../worktrees.js';
|
|
24
24
|
import { upgrade, getCurrentVersion } from '../upgrade.js';
|
|
25
|
-
import { getPromptPreview, parseSendAtValue, parseScheduledTaskPayload, serializeScheduledTaskPayload } from '../task-schedule.js';
|
|
25
|
+
import { applyScheduledTaskUserEdit, getPromptPreview, parseSendAtValue, parseScheduledTaskPayload, serializeScheduledTaskPayload } from '../task-schedule.js';
|
|
26
26
|
import { EXIT_NO_RESTART, formatMemberLookupUnavailableMessage, formatRelativeTime, formatTaskScheduleLine, getDiscordUserIdFromUserOption, isDiscordMemberLookupUnavailable, isGuildMemberSearchResult, isThreadChannelType, printDiscordInstallUrlAndExit, resolveBotCredentials, resolveDiscordUserOption, sendDiscordMessageWithOptionalAttachment, } from '../cli-runner.js';
|
|
27
|
+
import { validateCliModelOption } from '../session-handler/model-utils.js';
|
|
27
28
|
const cliLogger = createLogger(LogPrefix.CLI);
|
|
28
29
|
const cli = goke();
|
|
29
30
|
cli
|
|
@@ -134,7 +135,7 @@ cli
|
|
|
134
135
|
.option('--model <model>', 'Model for the scheduled session, format provider/model (empty string clears)')
|
|
135
136
|
.option('--pre-run <command>', 'New pre-run command (empty string clears)')
|
|
136
137
|
.option('--allow-concurrency <enabled>', z.enum(['true', 'false']).describe('Allow concurrent sessions: true or false'))
|
|
137
|
-
.option('-u, --user <user>', 'Discord user ID, mention, or username added to the task thread')
|
|
138
|
+
.option('-u, --user <user>', 'Discord user ID, mention, or username added to the task thread (empty string clears)')
|
|
138
139
|
.action(async (id, options) => {
|
|
139
140
|
try {
|
|
140
141
|
const trimmedPrompt = options.prompt === undefined ? undefined : options.prompt.trim();
|
|
@@ -142,9 +143,10 @@ cli
|
|
|
142
143
|
const hasModel = options.model !== undefined;
|
|
143
144
|
const hasPreRun = options.preRun !== undefined;
|
|
144
145
|
const hasAllowConcurrency = options.allowConcurrency !== undefined;
|
|
146
|
+
const hasUser = options.user !== undefined;
|
|
145
147
|
if (!trimmedPrompt &&
|
|
146
148
|
!options.sendAt &&
|
|
147
|
-
!
|
|
149
|
+
!hasUser &&
|
|
148
150
|
!hasAgent &&
|
|
149
151
|
!hasModel &&
|
|
150
152
|
!hasPreRun &&
|
|
@@ -180,7 +182,7 @@ cli
|
|
|
180
182
|
cliLogger.error(`Failed to parse task payload: ${existingPayload.message}`);
|
|
181
183
|
process.exit(EXIT_NO_RESTART);
|
|
182
184
|
}
|
|
183
|
-
const resolvedUser = options.user
|
|
185
|
+
const resolvedUser = hasUser && options.user.trim()
|
|
184
186
|
? await resolveTaskUser({
|
|
185
187
|
user: options.user,
|
|
186
188
|
channelId: task.channel_id,
|
|
@@ -191,23 +193,38 @@ cli
|
|
|
191
193
|
process.exit(EXIT_NO_RESTART);
|
|
192
194
|
}
|
|
193
195
|
const newPrompt = trimmedPrompt ?? existingPayload.prompt;
|
|
196
|
+
const nextModel = hasModel ? options.model.trim() || null : existingPayload.model;
|
|
197
|
+
if (nextModel) {
|
|
198
|
+
const modelCheck = await validateCliModelOption({
|
|
199
|
+
model: nextModel,
|
|
200
|
+
directory: task.project_directory ?? undefined,
|
|
201
|
+
});
|
|
202
|
+
if (modelCheck instanceof Error) {
|
|
203
|
+
cliLogger.error(modelCheck.message);
|
|
204
|
+
process.exit(EXIT_NO_RESTART);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
194
207
|
// Match send --model/--agent: empty string clears the override (null).
|
|
195
208
|
const updatedPayload = {
|
|
196
209
|
...existingPayload,
|
|
197
210
|
prompt: newPrompt,
|
|
198
|
-
...(resolvedUser
|
|
199
|
-
? { userId: resolvedUser.id, username: resolvedUser.username || null }
|
|
200
|
-
: {}),
|
|
201
211
|
...(hasAgent ? { agent: options.agent.trim() || null } : {}),
|
|
202
|
-
...(hasModel ? { model:
|
|
212
|
+
...(hasModel ? { model: nextModel } : {}),
|
|
203
213
|
...(hasPreRun ? { preRunCommand: options.preRun.trim() || null } : {}),
|
|
204
214
|
...(hasAllowConcurrency
|
|
205
215
|
? { allowConcurrency: options.allowConcurrency === 'true' }
|
|
206
216
|
: {}),
|
|
207
217
|
};
|
|
218
|
+
const payloadWithUser = hasUser
|
|
219
|
+
? applyScheduledTaskUserEdit({
|
|
220
|
+
payload: updatedPayload,
|
|
221
|
+
userOption: options.user,
|
|
222
|
+
resolvedUser,
|
|
223
|
+
})
|
|
224
|
+
: updatedPayload;
|
|
208
225
|
const updateData = {
|
|
209
226
|
taskId,
|
|
210
|
-
payloadJson: serializeScheduledTaskPayload(
|
|
227
|
+
payloadJson: serializeScheduledTaskPayload(payloadWithUser),
|
|
211
228
|
promptPreview: getPromptPreview(newPrompt),
|
|
212
229
|
};
|
|
213
230
|
if (options.sendAt) {
|
|
@@ -233,19 +250,21 @@ cli
|
|
|
233
250
|
process.exit(EXIT_NO_RESTART);
|
|
234
251
|
}
|
|
235
252
|
const parts = [`Updated task ${taskId}`];
|
|
236
|
-
if (
|
|
237
|
-
parts.push(
|
|
253
|
+
if (hasUser) {
|
|
254
|
+
parts.push(payloadWithUser.userId
|
|
255
|
+
? `user ${payloadWithUser.username || payloadWithUser.userId} will be added to the thread`
|
|
256
|
+
: 'user cleared; nobody will be added to the thread');
|
|
238
257
|
}
|
|
239
258
|
if (hasAgent) {
|
|
240
|
-
parts.push(`agent=${
|
|
259
|
+
parts.push(`agent=${payloadWithUser.agent || '-'}`);
|
|
241
260
|
}
|
|
242
261
|
if (hasModel) {
|
|
243
|
-
parts.push(`model=${
|
|
262
|
+
parts.push(`model=${payloadWithUser.model || '-'}`);
|
|
244
263
|
}
|
|
245
264
|
if (hasPreRun)
|
|
246
|
-
parts.push(`preRun=${
|
|
265
|
+
parts.push(`preRun=${payloadWithUser.preRunCommand || '-'}`);
|
|
247
266
|
if (hasAllowConcurrency) {
|
|
248
|
-
parts.push(`allowConcurrency=${
|
|
267
|
+
parts.push(`allowConcurrency=${payloadWithUser.allowConcurrency}`);
|
|
249
268
|
}
|
|
250
269
|
cliLogger.log(parts.join(' | '));
|
|
251
270
|
process.exit(0);
|