spec-cat 0.1.63 → 0.1.65
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/.output/nitro.json +1 -1
- package/.output/public/_nuxt/B-bFNWCF.js +43 -0
- package/.output/public/_nuxt/{D4LyIv_t.js → BEWSHKZd.js} +1 -1
- package/.output/public/_nuxt/{Su-Cl_JF.js → BHhMtuRK.js} +1 -1
- package/.output/public/_nuxt/{DwpuOtAP.js → BwRpECPL.js} +1 -1
- package/.output/public/_nuxt/{FfyCVKRU.js → CC_9aMCT.js} +1 -1
- package/.output/public/_nuxt/CHHbeXzh.js +1 -0
- package/.output/public/_nuxt/{DIOhMCOf.js → CzV__9yP.js} +3 -3
- package/.output/public/_nuxt/{Bz2rU2zs.js → DWDrIutw.js} +1 -1
- package/.output/public/_nuxt/D_4Ow2w2.js +1 -0
- package/.output/public/_nuxt/D_T_F7lf.js +1 -0
- package/.output/public/_nuxt/{DPOGSCeV.js → DqFq1MXh.js} +1 -1
- package/.output/public/_nuxt/YzuyFhrD.js +174 -0
- package/.output/public/_nuxt/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/cdc4ded8-9ff7-4e22-abbe-3b8c94caa781.json +1 -0
- package/.output/public/_nuxt/default.Cyeq1Er8.css +1 -0
- package/.output/public/_nuxt/entry.uBSFaL6A.css +1 -0
- package/.output/public/_nuxt/{useTheme.Cny4Z64J.css → useTheme.B4wINCoW.css} +1 -1
- package/.output/public/_nuxt/uyJF9wDo.js +58 -0
- package/.output/server/chunks/_/aiProviderRegistry.mjs +1 -2
- package/.output/server/chunks/_/aiProviderRegistry.mjs.map +1 -1
- package/.output/server/chunks/_/aiProviderSelection.mjs +2 -2
- package/.output/server/chunks/_/chatGit.mjs +1 -2
- package/.output/server/chunks/_/chatGit.mjs.map +1 -1
- package/.output/server/chunks/_/claudeProvider.mjs +1 -215
- package/.output/server/chunks/_/claudeProvider.mjs.map +1 -1
- package/.output/server/chunks/_/claudeService.mjs +50 -54
- package/.output/server/chunks/_/claudeService.mjs.map +1 -1
- package/.output/server/chunks/_/codexProvider.mjs +47 -812
- package/.output/server/chunks/_/codexProvider.mjs.map +1 -1
- package/.output/server/chunks/_/git.mjs +1 -2
- package/.output/server/chunks/_/git.mjs.map +1 -1
- package/.output/server/chunks/_/gitApiHelpers.mjs +1 -1
- package/.output/server/chunks/_/gitBranchOperations.mjs +1 -1
- package/.output/server/chunks/_/interactiveProviderQuery.mjs +160 -0
- package/.output/server/chunks/_/interactiveProviderQuery.mjs.map +1 -0
- package/.output/server/chunks/_/jobPersister.mjs +37 -6
- package/.output/server/chunks/_/jobPersister.mjs.map +1 -1
- package/.output/server/chunks/_/jobQueue.mjs +430 -278
- package/.output/server/chunks/_/jobQueue.mjs.map +1 -1
- package/.output/server/chunks/_/terminalSessions.mjs +582 -0
- package/.output/server/chunks/_/terminalSessions.mjs.map +1 -0
- package/.output/server/chunks/_/worktreeSetup.mjs +98 -0
- package/.output/server/chunks/_/worktreeSetup.mjs.map +1 -0
- package/.output/server/chunks/build/client.precomputed.mjs +1 -1
- package/.output/server/chunks/build/client.precomputed.mjs.map +1 -1
- package/.output/server/chunks/nitro/nitro.mjs +1227 -620
- package/.output/server/chunks/nitro/nitro.mjs.map +1 -1
- package/.output/server/chunks/routes/_ws.mjs +21 -111
- package/.output/server/chunks/routes/_ws.mjs.map +1 -1
- package/.output/server/chunks/routes/api/ai/providers.get.mjs +3 -1
- package/.output/server/chunks/routes/api/ai/providers.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/chat/compare.get.mjs +4 -5
- package/.output/server/chunks/routes/api/chat/compare.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/chat/context.get.mjs +4 -2
- package/.output/server/chunks/routes/api/chat/context.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/chat/finalize.post.mjs +11 -7
- package/.output/server/chunks/routes/api/chat/finalize.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/chat/generate-commit-message.post.mjs +26 -29
- package/.output/server/chunks/routes/api/chat/generate-commit-message.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/chat/preview-sync.post.mjs +4 -5
- package/.output/server/chunks/routes/api/chat/preview-sync.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/chat/preview.delete.mjs +4 -5
- package/.output/server/chunks/routes/api/chat/preview.delete.mjs.map +1 -1
- package/.output/server/chunks/routes/api/chat/preview.post.mjs +4 -6
- package/.output/server/chunks/routes/api/chat/preview.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/chat/rebase.post.mjs +4 -7
- package/.output/server/chunks/routes/api/chat/rebase.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/chat/worktree-commit.post.mjs +4 -6
- package/.output/server/chunks/routes/api/chat/worktree-commit.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/chat/worktree.delete.mjs +11 -4
- package/.output/server/chunks/routes/api/chat/worktree.delete.mjs.map +1 -1
- package/.output/server/chunks/routes/api/chat/worktree.post.mjs +4 -6
- package/.output/server/chunks/routes/api/chat/worktree.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/conversations/_conversationId/archive.post.mjs +10 -9
- package/.output/server/chunks/routes/api/conversations/_conversationId/archive.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/conversations/archives/_archiveId/restore.post.mjs +4 -7
- package/.output/server/chunks/routes/api/conversations/archives/_archiveId/restore.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/conversations/archives/_archiveId_.delete.mjs +4 -7
- package/.output/server/chunks/routes/api/conversations/archives/_archiveId_.delete.mjs.map +1 -1
- package/.output/server/chunks/routes/api/conversations/archives.delete.mjs +40 -0
- package/.output/server/chunks/routes/api/conversations/archives.delete.mjs.map +1 -0
- package/.output/server/chunks/routes/api/conversations/archives.get.mjs +4 -7
- package/.output/server/chunks/routes/api/conversations/archives.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/conversations/update.post.mjs +4 -7
- package/.output/server/chunks/routes/api/conversations/update.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/conversations.get.mjs +4 -7
- package/.output/server/chunks/routes/api/conversations.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/conversations.post.mjs +4 -7
- package/.output/server/chunks/routes/api/conversations.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/cwd.get.mjs +4 -2
- package/.output/server/chunks/routes/api/cwd.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/branch-rename.post.mjs +3 -4
- package/.output/server/chunks/routes/api/git/branch-rename.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/branch.delete.mjs +4 -5
- package/.output/server/chunks/routes/api/git/branch.delete.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/branches.get.mjs +4 -6
- package/.output/server/chunks/routes/api/git/branches.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/checkout.post.mjs +4 -5
- package/.output/server/chunks/routes/api/git/checkout.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/cherry-pick.post.mjs +3 -4
- package/.output/server/chunks/routes/api/git/cherry-pick.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/clean.post.mjs +3 -4
- package/.output/server/chunks/routes/api/git/clean.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/commit/_id_.get.mjs +3 -4
- package/.output/server/chunks/routes/api/git/commit/_id_.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/commit.post.mjs +4 -5
- package/.output/server/chunks/routes/api/git/commit.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/diff.get.mjs +4 -5
- package/.output/server/chunks/routes/api/git/diff.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/fetch.post.mjs +3 -4
- package/.output/server/chunks/routes/api/git/fetch.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/file-diff.get.mjs +4 -5
- package/.output/server/chunks/routes/api/git/file-diff.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/graph.get.mjs +4 -5
- package/.output/server/chunks/routes/api/git/graph.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/log.get.mjs +4 -5
- package/.output/server/chunks/routes/api/git/log.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/merge-base.get.mjs +4 -5
- package/.output/server/chunks/routes/api/git/merge-base.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/merge.post.mjs +3 -4
- package/.output/server/chunks/routes/api/git/merge.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/pull.post.mjs +3 -4
- package/.output/server/chunks/routes/api/git/pull.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/push.post.mjs +3 -4
- package/.output/server/chunks/routes/api/git/push.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/rebase.post.mjs +3 -4
- package/.output/server/chunks/routes/api/git/rebase.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/remote.delete.mjs +3 -4
- package/.output/server/chunks/routes/api/git/remote.delete.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/remote.post.mjs +3 -4
- package/.output/server/chunks/routes/api/git/remote.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/remote.put.mjs +3 -4
- package/.output/server/chunks/routes/api/git/remote.put.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/remotes.get.mjs +3 -4
- package/.output/server/chunks/routes/api/git/remotes.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/reset.post.mjs +3 -4
- package/.output/server/chunks/routes/api/git/reset.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/revert.post.mjs +3 -4
- package/.output/server/chunks/routes/api/git/revert.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/show.get.mjs +4 -5
- package/.output/server/chunks/routes/api/git/show.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/stage.post.mjs +4 -5
- package/.output/server/chunks/routes/api/git/stage.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/stash-apply.post.mjs +3 -4
- package/.output/server/chunks/routes/api/git/stash-apply.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/stash-branch.post.mjs +3 -4
- package/.output/server/chunks/routes/api/git/stash-branch.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/stash-drop.post.mjs +3 -4
- package/.output/server/chunks/routes/api/git/stash-drop.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/stash-pop.post.mjs +3 -4
- package/.output/server/chunks/routes/api/git/stash-pop.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/stash.get.mjs +3 -4
- package/.output/server/chunks/routes/api/git/stash.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/stash.post.mjs +3 -4
- package/.output/server/chunks/routes/api/git/stash.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/state.get.mjs +3 -4
- package/.output/server/chunks/routes/api/git/state.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/status.get.mjs +4 -5
- package/.output/server/chunks/routes/api/git/status.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/tag/_name_.get.mjs +3 -4
- package/.output/server/chunks/routes/api/git/tag/_name_.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/tag-push.post.mjs +3 -4
- package/.output/server/chunks/routes/api/git/tag-push.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/tag.delete.mjs +3 -4
- package/.output/server/chunks/routes/api/git/tag.delete.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/tag.post.mjs +3 -4
- package/.output/server/chunks/routes/api/git/tag.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/git/unstage.post.mjs +4 -5
- package/.output/server/chunks/routes/api/git/unstage.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/index.get.mjs +7 -4
- package/.output/server/chunks/routes/api/index.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/index.get2.mjs +4 -2
- package/.output/server/chunks/routes/api/index.get2.mjs.map +1 -1
- package/.output/server/chunks/routes/api/index.get3.mjs +4 -2
- package/.output/server/chunks/routes/api/index.get3.mjs.map +1 -1
- package/.output/server/chunks/routes/api/index.post.mjs +19 -16
- package/.output/server/chunks/routes/api/index.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/index.post2.mjs +4 -6
- package/.output/server/chunks/routes/api/index.post2.mjs.map +1 -1
- package/.output/server/chunks/routes/api/jobs/_id/cancel.post.mjs +6 -3
- package/.output/server/chunks/routes/api/jobs/_id/cancel.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/jobs/_id_.get.mjs +7 -4
- package/.output/server/chunks/routes/api/jobs/_id_.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/rebase/abort.post.mjs +4 -2
- package/.output/server/chunks/routes/api/rebase/abort.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/rebase/ai-resolve.post.mjs +19 -105
- package/.output/server/chunks/routes/api/rebase/ai-resolve.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/rebase/conflicts.get.mjs +3 -1
- package/.output/server/chunks/routes/api/rebase/conflicts.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/rebase/continue-sync.post.mjs +4 -2
- package/.output/server/chunks/routes/api/rebase/continue-sync.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/rebase/continue.post.mjs +71 -73
- package/.output/server/chunks/routes/api/rebase/continue.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/rebase/resolve.put.mjs +3 -1
- package/.output/server/chunks/routes/api/rebase/resolve.put.mjs.map +1 -1
- package/.output/server/chunks/routes/api/repository/status.get.mjs +4 -5
- package/.output/server/chunks/routes/api/repository/status.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/settings.get.mjs +6 -5
- package/.output/server/chunks/routes/api/settings.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/settings.post.mjs +5 -3
- package/.output/server/chunks/routes/api/settings.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/skills/_skillId/prompt.post.mjs +3 -1
- package/.output/server/chunks/routes/api/skills/_skillId/prompt.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/specs/_featureId/_...filename_.get.mjs +3 -1
- package/.output/server/chunks/routes/api/specs/_featureId/_...filename_.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/specs/_featureId/_...filename_.put.mjs +3 -1
- package/.output/server/chunks/routes/api/specs/_featureId/_...filename_.put.mjs.map +1 -1
- package/.output/server/chunks/routes/api/specs/features.get.mjs +3 -1
- package/.output/server/chunks/routes/api/specs/features.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/specs/reindex.post.mjs +4 -2
- package/.output/server/chunks/routes/api/specs/reindex.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/specs/search.get.mjs +4 -2
- package/.output/server/chunks/routes/api/specs/search.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/specs/status.get.mjs +4 -2
- package/.output/server/chunks/routes/api/specs/status.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/specs/traceability/_featureId_.get.mjs +4 -2
- package/.output/server/chunks/routes/api/specs/traceability/_featureId_.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/worktrees/_name/switch.post.mjs +4 -2
- package/.output/server/chunks/routes/api/worktrees/_name/switch.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/worktrees/_name_.delete.mjs +3 -1
- package/.output/server/chunks/routes/api/worktrees/_name_.delete.mjs.map +1 -1
- package/.output/server/chunks/routes/git-watcher-ws.mjs +4 -2
- package/.output/server/chunks/routes/git-watcher-ws.mjs.map +1 -1
- package/.output/server/chunks/routes/renderer.mjs +4 -2
- package/.output/server/chunks/routes/terminal-ws.mjs +230 -0
- package/.output/server/chunks/routes/terminal-ws.mjs.map +1 -0
- package/.output/server/index.mjs +4 -2
- package/.output/server/index.mjs.map +1 -1
- package/.output/server/node_modules/node-pty/build/Makefile +369 -0
- package/.output/server/node_modules/node-pty/build/Release/pty.node +0 -0
- package/.output/server/node_modules/node-pty/build/binding.Makefile +6 -0
- package/.output/server/node_modules/node-pty/build/config.gypi +513 -0
- package/.output/server/node_modules/node-pty/build/pty.target.mk +168 -0
- package/.output/server/node_modules/node-pty/lib/eventEmitter2.js +47 -0
- package/.output/server/node_modules/node-pty/lib/index.js +52 -0
- package/.output/server/node_modules/node-pty/lib/shared/conout.js +11 -0
- package/.output/server/node_modules/node-pty/lib/terminal.js +190 -0
- package/.output/server/node_modules/node-pty/lib/unixTerminal.js +346 -0
- package/.output/server/node_modules/node-pty/lib/utils.js +39 -0
- package/.output/server/node_modules/node-pty/lib/windowsConoutConnection.js +125 -0
- package/.output/server/node_modules/node-pty/lib/windowsPtyAgent.js +320 -0
- package/.output/server/node_modules/node-pty/lib/windowsTerminal.js +199 -0
- package/.output/server/node_modules/node-pty/package.json +64 -0
- package/.output/server/package.json +2 -1
- package/package.json +6 -1
- package/.output/public/_nuxt/2zamijve.js +0 -1
- package/.output/public/_nuxt/BEjIzkrM.js +0 -85
- package/.output/public/_nuxt/BaSsfm-t.js +0 -36
- package/.output/public/_nuxt/BjdLSVUw.js +0 -1
- package/.output/public/_nuxt/DeqM2TrP.js +0 -1
- package/.output/public/_nuxt/builds/meta/74089fa1-2fbb-4792-94aa-7535d2aec557.json +0 -1
- package/.output/public/_nuxt/default.DKSPicGu.css +0 -1
- package/.output/public/_nuxt/entry.BUDfUMkE.css +0 -1
- package/.output/public/_nuxt/zrVRZSxq.js +0 -64
- package/.output/server/chunks/_/baseBranch.mjs +0 -137
- package/.output/server/chunks/_/baseBranch.mjs.map +0 -1
- package/.output/server/chunks/_/claude.mjs +0 -164
- package/.output/server/chunks/_/claude.mjs.map +0 -1
- package/.output/server/chunks/_/conversationStore.mjs +0 -257
- package/.output/server/chunks/_/conversationStore.mjs.map +0 -1
- package/.output/server/chunks/_/geminiProvider.mjs +0 -372
- package/.output/server/chunks/_/geminiProvider.mjs.map +0 -1
- package/.output/server/chunks/_/gitExec.mjs +0 -48
- package/.output/server/chunks/_/gitExec.mjs.map +0 -1
- package/.output/server/chunks/_/providerProcessError.mjs +0 -163
- package/.output/server/chunks/_/providerProcessError.mjs.map +0 -1
- package/.output/server/chunks/_/uiAdapter.mjs +0 -421
- package/.output/server/chunks/_/uiAdapter.mjs.map +0 -1
- package/.output/server/chunks/routes/api/chat.post.mjs +0 -276
- package/.output/server/chunks/routes/api/chat.post.mjs.map +0 -1
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
1
|
+
import { existsSync, statSync, openSync, readSync, closeSync } from 'node:fs';
|
|
2
2
|
import { e as eventBus, G as GLOBAL_CHANNEL } from './eventBus.mjs';
|
|
3
|
-
import {
|
|
3
|
+
import { q as readConversationFromStorage, a as getProjectDir, s as updateConversationProviderSessionInStorage } from '../nitro/nitro.mjs';
|
|
4
4
|
import { exec } from 'node:child_process';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
6
|
import { i as isSpecCatWorktreePath, b as getChatWorktreeIdFromPath } from './worktreePaths.mjs';
|
|
7
7
|
import { join } from 'node:path';
|
|
8
|
-
import { r as resolveServerProviderSelection
|
|
9
|
-
import { s as streamChatWithProvider, h as hasCodexPermissionError, a as hasCodexMissingRolloutPathError, b as summarizeProviderProcessError } from './providerProcessError.mjs';
|
|
8
|
+
import { r as resolveServerProviderSelection } from './aiProviderSelection.mjs';
|
|
10
9
|
import { g as getProvider } from './aiProviderRegistry.mjs';
|
|
11
|
-
import {
|
|
10
|
+
import { g as getCliHookSpoolPath, p as parseCliHookRecord, d as disposeTerminalSession, b as buildTerminalSessionId, s as subscribeTerminalSession, w as writeTerminalInput, a as getOrCreateTerminalSession } from './terminalSessions.mjs';
|
|
12
11
|
|
|
13
12
|
const execAsync = promisify(exec);
|
|
14
13
|
async function ensureChatWorktree(projectDir, worktreePath, knownBranch) {
|
|
@@ -83,62 +82,179 @@ async function loadSpecContext(projectDir, featureId) {
|
|
|
83
82
|
return sections.join("\n");
|
|
84
83
|
}
|
|
85
84
|
|
|
86
|
-
function
|
|
87
|
-
|
|
88
|
-
if (event.blockId) return `block:${event.blockId}`;
|
|
89
|
-
return null;
|
|
85
|
+
function asRecord(value) {
|
|
86
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
90
87
|
}
|
|
91
|
-
function
|
|
92
|
-
const
|
|
93
|
-
|
|
88
|
+
function pickString(record, keys) {
|
|
89
|
+
for (const key of keys) {
|
|
90
|
+
const value = record[key];
|
|
91
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
92
|
+
}
|
|
93
|
+
return void 0;
|
|
94
94
|
}
|
|
95
|
-
function
|
|
96
|
-
const
|
|
97
|
-
if (
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
95
|
+
function extractPrompt(payload) {
|
|
96
|
+
const direct = pickString(payload, ["prompt", "message", "user_prompt", "userPrompt", "input"]);
|
|
97
|
+
if (direct) return direct;
|
|
98
|
+
const nestedKeys = ["tool_input", "toolInput", "request", "data"];
|
|
99
|
+
for (const key of nestedKeys) {
|
|
100
|
+
const nested = asRecord(payload[key]);
|
|
101
|
+
const nestedPrompt = pickString(nested, ["prompt", "message", "input"]);
|
|
102
|
+
if (nestedPrompt) return nestedPrompt;
|
|
102
103
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
104
|
+
return void 0;
|
|
105
|
+
}
|
|
106
|
+
function extractToolName(payload) {
|
|
107
|
+
const direct = pickString(payload, ["tool_name", "toolName", "name"]);
|
|
108
|
+
if (direct) return direct;
|
|
109
|
+
const tool = asRecord(payload.tool);
|
|
110
|
+
return pickString(tool, ["name", "tool_name", "toolName"]);
|
|
111
|
+
}
|
|
112
|
+
function isToolFailure(eventName) {
|
|
113
|
+
return eventName === "PostToolUseFailure" || eventName === "PermissionDenied";
|
|
114
|
+
}
|
|
115
|
+
function toEvents(record) {
|
|
116
|
+
const payload = asRecord(record.payload);
|
|
117
|
+
const eventName = record.hookEventName || pickString(payload, ["hook_event_name", "hookEventName"]) || "unknown";
|
|
118
|
+
const base = {
|
|
119
|
+
providerId: record.providerId,
|
|
120
|
+
jobId: record.jobId,
|
|
121
|
+
requestId: record.requestId,
|
|
122
|
+
hookEventName: eventName,
|
|
123
|
+
sessionId: record.sessionId,
|
|
124
|
+
turnId: record.turnId,
|
|
125
|
+
cwd: record.cwd,
|
|
126
|
+
timestamp: record.timestamp
|
|
127
|
+
};
|
|
128
|
+
const events = [
|
|
129
|
+
{
|
|
130
|
+
type: "cli_hook",
|
|
131
|
+
...base,
|
|
132
|
+
payload: record.payload
|
|
109
133
|
}
|
|
110
|
-
|
|
134
|
+
];
|
|
135
|
+
if (eventName === "UserPromptSubmit") {
|
|
136
|
+
events.push({
|
|
137
|
+
type: "cli_prompt_submitted",
|
|
138
|
+
...base,
|
|
139
|
+
prompt: extractPrompt(payload)
|
|
140
|
+
});
|
|
111
141
|
}
|
|
112
|
-
|
|
113
|
-
|
|
142
|
+
if (eventName === "PostToolUse" || eventName === "PostToolUseFailure") {
|
|
143
|
+
events.push({
|
|
144
|
+
type: "cli_tool_completed",
|
|
145
|
+
...base,
|
|
146
|
+
toolName: extractToolName(payload),
|
|
147
|
+
failed: isToolFailure(eventName)
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
if (eventName === "Stop" || eventName === "SubagentStop") {
|
|
151
|
+
events.push({
|
|
152
|
+
type: eventName === "Stop" ? "cli_turn_stop" : "cli_subagent_stop",
|
|
153
|
+
...base,
|
|
154
|
+
stopHookActive: payload.stop_hook_active,
|
|
155
|
+
lastAssistantMessage: pickString(payload, ["last_assistant_message", "lastAssistantMessage"])
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
return events;
|
|
114
159
|
}
|
|
115
|
-
function
|
|
160
|
+
function startCliHookMonitor(options) {
|
|
161
|
+
const spoolPath = getCliHookSpoolPath(options.conversationId);
|
|
162
|
+
let offset = 0;
|
|
163
|
+
let tail = Buffer.alloc(0);
|
|
164
|
+
let stopped = false;
|
|
165
|
+
let sawStopHook = false;
|
|
166
|
+
let sawArmedPrompt = false;
|
|
116
167
|
try {
|
|
117
|
-
|
|
118
|
-
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
168
|
+
offset = existsSync(spoolPath) ? statSync(spoolPath).size : 0;
|
|
119
169
|
} catch {
|
|
120
|
-
|
|
170
|
+
offset = 0;
|
|
121
171
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
172
|
+
const readAppendedBytes = (size) => {
|
|
173
|
+
const length = size - offset;
|
|
174
|
+
if (length <= 0) return null;
|
|
175
|
+
let fd = null;
|
|
176
|
+
try {
|
|
177
|
+
fd = openSync(spoolPath, "r");
|
|
178
|
+
const buffer = Buffer.alloc(length);
|
|
179
|
+
const bytesRead = readSync(fd, buffer, 0, length, offset);
|
|
180
|
+
offset += bytesRead;
|
|
181
|
+
return buffer.subarray(0, bytesRead);
|
|
182
|
+
} catch {
|
|
183
|
+
return null;
|
|
184
|
+
} finally {
|
|
185
|
+
if (fd !== null) {
|
|
186
|
+
try {
|
|
187
|
+
closeSync(fd);
|
|
188
|
+
} catch {
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
const poll = () => {
|
|
194
|
+
if (stopped || !existsSync(spoolPath)) return;
|
|
195
|
+
let size = 0;
|
|
196
|
+
try {
|
|
197
|
+
size = statSync(spoolPath).size;
|
|
198
|
+
} catch {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (size < offset) {
|
|
202
|
+
offset = 0;
|
|
203
|
+
tail = Buffer.alloc(0);
|
|
204
|
+
}
|
|
205
|
+
const chunk = readAppendedBytes(size);
|
|
206
|
+
if (!chunk || chunk.length === 0) return;
|
|
207
|
+
const combined = tail.length > 0 ? Buffer.concat([tail, chunk]) : chunk;
|
|
208
|
+
const lines = [];
|
|
209
|
+
let lineStart = 0;
|
|
210
|
+
for (let i = 0; i < combined.length; i++) {
|
|
211
|
+
if (combined[i] === 10) {
|
|
212
|
+
lines.push(combined.toString("utf-8", lineStart, i));
|
|
213
|
+
lineStart = i + 1;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
tail = lineStart < combined.length ? Buffer.from(combined.subarray(lineStart)) : Buffer.alloc(0);
|
|
217
|
+
for (const line of lines) {
|
|
218
|
+
const record = parseCliHookRecord(line);
|
|
219
|
+
if (!record) continue;
|
|
220
|
+
if (options.jobId !== void 0 && record.jobId !== options.jobId) continue;
|
|
221
|
+
const armed = options.isArmed ? options.isArmed() : true;
|
|
222
|
+
if (armed && record.hookEventName === "UserPromptSubmit") {
|
|
223
|
+
sawArmedPrompt = true;
|
|
224
|
+
}
|
|
225
|
+
const isStop = record.hookEventName === "Stop";
|
|
226
|
+
const promptGateOpen = sawArmedPrompt;
|
|
227
|
+
const firstStop = isStop && armed && promptGateOpen && !sawStopHook;
|
|
228
|
+
if (firstStop) {
|
|
229
|
+
sawStopHook = true;
|
|
230
|
+
}
|
|
231
|
+
for (const event of toEvents(record)) {
|
|
232
|
+
options.emit(event);
|
|
233
|
+
}
|
|
234
|
+
if (firstStop && options.onStop) {
|
|
235
|
+
const payload = asRecord(record.payload);
|
|
236
|
+
options.onStop(pickString(payload, ["last_assistant_message", "lastAssistantMessage"]));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
const interval = setInterval(poll, 100);
|
|
135
241
|
return {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
242
|
+
stop: () => {
|
|
243
|
+
poll();
|
|
244
|
+
stopped = true;
|
|
245
|
+
clearInterval(interval);
|
|
246
|
+
},
|
|
247
|
+
sawStop: () => sawStopHook
|
|
140
248
|
};
|
|
141
249
|
}
|
|
250
|
+
|
|
251
|
+
function normalizeToolName(tool) {
|
|
252
|
+
if (!tool) return "";
|
|
253
|
+
const trimmed = tool.trim();
|
|
254
|
+
if (!trimmed) return "";
|
|
255
|
+
return trimmed.charAt(0).toUpperCase() + trimmed.slice(1);
|
|
256
|
+
}
|
|
257
|
+
|
|
142
258
|
function approveTools(approvedTools, tools) {
|
|
143
259
|
for (const tool of tools) {
|
|
144
260
|
const normalized = normalizeToolName(tool);
|
|
@@ -153,6 +269,8 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
|
|
|
153
269
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
154
270
|
const MAX_ATTACHMENT_COUNT = 4;
|
|
155
271
|
const MAX_ATTACHMENT_SIZE_BYTES = 5 * 1024 * 1024;
|
|
272
|
+
const PTY_TURN_TIMEOUT_MS = 30 * 6e4;
|
|
273
|
+
const PTY_IDLE_FINALIZE_MS = 8e3;
|
|
156
274
|
const SPECKIT_AUTONOMY_DIRECTIVE = [
|
|
157
275
|
"Speckit Execution Mode (MANDATORY):",
|
|
158
276
|
"- Do not ask the user for confirmation, follow-up, or permission to proceed.",
|
|
@@ -208,6 +326,24 @@ function buildProviderMessage(baseMessage, attachments) {
|
|
|
208
326
|
function generateJobId() {
|
|
209
327
|
return `job-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
|
|
210
328
|
}
|
|
329
|
+
function getStoredProviderSessionId(conversation) {
|
|
330
|
+
var _a;
|
|
331
|
+
if (!conversation || typeof conversation !== "object") return null;
|
|
332
|
+
const record = conversation;
|
|
333
|
+
const value = (_a = record.providerSessionId) != null ? _a : record.claudeSessionId;
|
|
334
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
335
|
+
}
|
|
336
|
+
function getStoredProviderSelection(conversation) {
|
|
337
|
+
if (!conversation || typeof conversation !== "object") return null;
|
|
338
|
+
const record = conversation;
|
|
339
|
+
const providerId = record.providerId;
|
|
340
|
+
if (typeof providerId !== "string" || providerId.length === 0) return null;
|
|
341
|
+
const providerModelKey = record.providerModelKey;
|
|
342
|
+
return {
|
|
343
|
+
providerId,
|
|
344
|
+
providerModelKey: typeof providerModelKey === "string" ? providerModelKey : ""
|
|
345
|
+
};
|
|
346
|
+
}
|
|
211
347
|
class ChatJobQueue {
|
|
212
348
|
constructor() {
|
|
213
349
|
__publicField(this, "jobs", /* @__PURE__ */ new Map());
|
|
@@ -312,7 +448,7 @@ class ChatJobQueue {
|
|
|
312
448
|
source,
|
|
313
449
|
message: msg.message
|
|
314
450
|
});
|
|
315
|
-
this.runProvider(job, false
|
|
451
|
+
this.runProvider(job, false);
|
|
316
452
|
return job.id;
|
|
317
453
|
}
|
|
318
454
|
/**
|
|
@@ -340,7 +476,7 @@ class ChatJobQueue {
|
|
|
340
476
|
job.status = "running";
|
|
341
477
|
const resumeMode = job.message.permissionMode || "ask";
|
|
342
478
|
job.message = { ...job.message, permissionMode: resumeMode };
|
|
343
|
-
this.runProvider(job, false
|
|
479
|
+
this.runProvider(job, false);
|
|
344
480
|
} else {
|
|
345
481
|
procState.pendingTools = [];
|
|
346
482
|
this.setJobStatus(job, "done");
|
|
@@ -374,6 +510,35 @@ class ChatJobQueue {
|
|
|
374
510
|
this.setJobStatus(job, "done");
|
|
375
511
|
console.log("[JobQueue] Abort: job status set to done for", job.id);
|
|
376
512
|
}
|
|
513
|
+
/**
|
|
514
|
+
* Terminate any provider process associated with a conversation that has
|
|
515
|
+
* been finalized. Unlike abort(), this is cleanup for a read-only
|
|
516
|
+
* conversation, so it must not emit a stopped assistant turn.
|
|
517
|
+
*/
|
|
518
|
+
finalizeConversation(conversationId) {
|
|
519
|
+
disposeTerminalSession(buildTerminalSessionId(conversationId));
|
|
520
|
+
const convState = this.conversationStates.get(conversationId);
|
|
521
|
+
if (!convState) return;
|
|
522
|
+
if (convState.activeJobId) {
|
|
523
|
+
const job = this.jobs.get(convState.activeJobId);
|
|
524
|
+
const procState = this.jobProcessStates.get(convState.activeJobId);
|
|
525
|
+
if (procState == null ? void 0 : procState.proc) {
|
|
526
|
+
procState.procGeneration++;
|
|
527
|
+
killProc(procState.proc);
|
|
528
|
+
procState.proc = null;
|
|
529
|
+
console.log("[JobQueue] Finalize: process killed for", convState.activeJobId);
|
|
530
|
+
}
|
|
531
|
+
if (procState) {
|
|
532
|
+
procState.pendingTools = [];
|
|
533
|
+
}
|
|
534
|
+
if (job && job.status !== "done" && job.status !== "error") {
|
|
535
|
+
this.setJobStatus(job, "done");
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
convState.providerSessionId = null;
|
|
539
|
+
convState.approvedTools.clear();
|
|
540
|
+
convState.activeJobId = null;
|
|
541
|
+
}
|
|
377
542
|
/**
|
|
378
543
|
* Reset provider session state for a conversation.
|
|
379
544
|
*/
|
|
@@ -415,13 +580,90 @@ class ChatJobQueue {
|
|
|
415
580
|
return Array.from(this.jobs.values());
|
|
416
581
|
}
|
|
417
582
|
// ── Provider Execution ─────────────────────────────
|
|
418
|
-
|
|
583
|
+
/**
|
|
584
|
+
* Execute a job through the provider's interactive PTY session. Turn
|
|
585
|
+
* completion is detected via the CLI Stop hook.
|
|
586
|
+
*/
|
|
587
|
+
async runProvider(job, isRetry) {
|
|
588
|
+
return this.runProviderViaPty(job, isRetry);
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Inject a prompt into a (possibly freshly-spawned) PTY session once its TUI
|
|
592
|
+
* composer is ready, then submit it. The prompt is bracketed-pasted so
|
|
593
|
+
* embedded newlines don't submit line-by-line. Server-side and
|
|
594
|
+
* client-independent — runs whether or not a terminal-ws viewer is attached.
|
|
595
|
+
*/
|
|
596
|
+
injectMessage(session, sessionId, text, stillValid, onSubmitted) {
|
|
597
|
+
const READY_IDLE_MS = 1200;
|
|
598
|
+
const REUSED_READY_MS = 800;
|
|
599
|
+
const READY_MAX_MS = 15e3;
|
|
600
|
+
const SUBMIT_IDLE_MS = 600;
|
|
601
|
+
const SUBMIT_MAX_MS = 4e3;
|
|
602
|
+
const reused = session.buffer.length > 0;
|
|
603
|
+
const safeText = text.replace(/\x1b\[20[01]~/g, "");
|
|
604
|
+
const startedAt = Date.now();
|
|
605
|
+
let firstDataAt = reused ? startedAt : 0;
|
|
606
|
+
let lastDataAt = Date.now();
|
|
607
|
+
let injected = false;
|
|
608
|
+
let injectedAt = 0;
|
|
609
|
+
let submitted = false;
|
|
610
|
+
const unsubscribe = subscribeTerminalSession(session, {
|
|
611
|
+
onData: () => {
|
|
612
|
+
const now = Date.now();
|
|
613
|
+
if (!firstDataAt) firstDataAt = now;
|
|
614
|
+
lastDataAt = now;
|
|
615
|
+
},
|
|
616
|
+
onExit: () => {
|
|
617
|
+
}
|
|
618
|
+
});
|
|
619
|
+
const timer = setInterval(() => {
|
|
620
|
+
if (!stillValid()) {
|
|
621
|
+
clearInterval(timer);
|
|
622
|
+
unsubscribe();
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
const now = Date.now();
|
|
626
|
+
if (!injected) {
|
|
627
|
+
const idleMs = reused ? REUSED_READY_MS : READY_IDLE_MS;
|
|
628
|
+
const ready = firstDataAt > 0 && now - lastDataAt >= idleMs;
|
|
629
|
+
const forced = now - startedAt >= READY_MAX_MS;
|
|
630
|
+
if (ready || forced) {
|
|
631
|
+
injected = true;
|
|
632
|
+
injectedAt = now;
|
|
633
|
+
lastDataAt = now;
|
|
634
|
+
writeTerminalInput(sessionId, `\x1B[200~${safeText}\x1B[201~`);
|
|
635
|
+
}
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
if (!submitted) {
|
|
639
|
+
const echoSettled = now - lastDataAt >= SUBMIT_IDLE_MS;
|
|
640
|
+
const submitForced = now - injectedAt >= SUBMIT_MAX_MS;
|
|
641
|
+
if (echoSettled || submitForced) {
|
|
642
|
+
submitted = true;
|
|
643
|
+
writeTerminalInput(sessionId, "\r");
|
|
644
|
+
onSubmitted();
|
|
645
|
+
clearInterval(timer);
|
|
646
|
+
unsubscribe();
|
|
647
|
+
}
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
}, 250);
|
|
651
|
+
}
|
|
652
|
+
async runProviderViaPty(job, isRetry) {
|
|
419
653
|
const msg = job.message;
|
|
420
654
|
const convState = this.getConversationState(job.conversationId);
|
|
421
655
|
const procState = this.jobProcessStates.get(job.id);
|
|
422
656
|
if (!procState) return;
|
|
423
657
|
job.status = "running";
|
|
424
|
-
|
|
658
|
+
let requestedSelection;
|
|
659
|
+
if (msg.providerId) {
|
|
660
|
+
requestedSelection = { providerId: msg.providerId, modelKey: msg.providerModelKey || "" };
|
|
661
|
+
} else {
|
|
662
|
+
const storedSelection = getStoredProviderSelection(
|
|
663
|
+
await readConversationFromStorage(job.conversationId)
|
|
664
|
+
);
|
|
665
|
+
requestedSelection = storedSelection ? { providerId: storedSelection.providerId, modelKey: msg.providerModelKey || storedSelection.providerModelKey } : { providerId: "claude", modelKey: msg.providerModelKey || "" };
|
|
666
|
+
}
|
|
425
667
|
const selection = await resolveServerProviderSelection(requestedSelection);
|
|
426
668
|
const provider = getProvider(selection.providerId);
|
|
427
669
|
if (!provider) {
|
|
@@ -433,39 +675,8 @@ class ChatJobQueue {
|
|
|
433
675
|
this.setJobStatus(job, "error");
|
|
434
676
|
return;
|
|
435
677
|
}
|
|
436
|
-
const providerGuard = await guardProviderCapability(
|
|
437
|
-
selection,
|
|
438
|
-
"streaming",
|
|
439
|
-
"Choose a provider with streaming capability in Settings."
|
|
440
|
-
);
|
|
441
|
-
if ("failure" in providerGuard) {
|
|
442
|
-
this.emitAndBuffer(job, {
|
|
443
|
-
type: "error",
|
|
444
|
-
error: providerGuard.failure.error,
|
|
445
|
-
requestId: msg.requestId
|
|
446
|
-
});
|
|
447
|
-
this.setJobStatus(job, "error");
|
|
448
|
-
return;
|
|
449
|
-
}
|
|
450
678
|
const projectDir = getProjectDir();
|
|
451
679
|
const workingDirectory = msg.cwd || projectDir;
|
|
452
|
-
const mode = msg.permissionMode || "ask";
|
|
453
|
-
if (mode === "ask" || mode === "plan") {
|
|
454
|
-
const permissionGuard = await guardProviderCapability(
|
|
455
|
-
selection,
|
|
456
|
-
"permissions",
|
|
457
|
-
"Use auto/bypass permission mode or choose a provider that supports permission prompts."
|
|
458
|
-
);
|
|
459
|
-
if ("failure" in permissionGuard) {
|
|
460
|
-
this.emitAndBuffer(job, {
|
|
461
|
-
type: "error",
|
|
462
|
-
error: permissionGuard.failure.error,
|
|
463
|
-
requestId: msg.requestId
|
|
464
|
-
});
|
|
465
|
-
this.setJobStatus(job, "error");
|
|
466
|
-
return;
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
680
|
if (isSpecCatWorktreePath(workingDirectory) && !existsSync(workingDirectory)) {
|
|
470
681
|
const result = await ensureChatWorktree(projectDir, workingDirectory, msg.worktreeBranch);
|
|
471
682
|
if (result.recovered) {
|
|
@@ -480,224 +691,165 @@ class ChatJobQueue {
|
|
|
480
691
|
return;
|
|
481
692
|
}
|
|
482
693
|
}
|
|
694
|
+
const speckitCommand = isSpeckitCommand(msg.message);
|
|
695
|
+
if (!isRetry && !speckitCommand && !convState.providerSessionId) {
|
|
696
|
+
const storedSessionId = getStoredProviderSessionId(await readConversationFromStorage(job.conversationId));
|
|
697
|
+
if (storedSessionId) convState.providerSessionId = storedSessionId;
|
|
698
|
+
}
|
|
483
699
|
const usedResumeFlag = !isRetry && !!convState.providerSessionId;
|
|
484
700
|
const resumeSessionId = usedResumeFlag ? convState.providerSessionId : void 0;
|
|
485
|
-
let
|
|
486
|
-
const speckitCommand = isSpeckitCommand(msg.message);
|
|
701
|
+
let preface = "";
|
|
487
702
|
if (msg.featureId && !usedResumeFlag) {
|
|
488
703
|
try {
|
|
489
704
|
const specContext = await loadSpecContext(projectDir, msg.featureId);
|
|
490
|
-
if (specContext)
|
|
491
|
-
systemPrompt = specContext;
|
|
492
|
-
}
|
|
705
|
+
if (specContext) preface = specContext;
|
|
493
706
|
} catch (error) {
|
|
494
707
|
console.error("[JobQueue] Failed to load spec context:", error);
|
|
495
708
|
}
|
|
496
709
|
}
|
|
497
710
|
if (speckitCommand && !usedResumeFlag) {
|
|
498
|
-
|
|
711
|
+
preface = preface ? `${preface}
|
|
499
712
|
|
|
500
713
|
${SPECKIT_AUTONOMY_DIRECTIVE}` : SPECKIT_AUTONOMY_DIRECTIVE;
|
|
501
714
|
}
|
|
502
|
-
console.log("[JobQueue] Running provider:", selection.providerId, selection.modelKey, isRetry ? "(retry)" : "");
|
|
503
|
-
const generation = procState.procGeneration;
|
|
504
|
-
let permissionRequested = false;
|
|
505
|
-
let emittedRenderableContent = false;
|
|
506
|
-
let emittedTerminalErrorEvent = false;
|
|
507
|
-
const activeToolInputs = /* @__PURE__ */ new Map();
|
|
508
715
|
const attachments = msg.attachments || [];
|
|
509
|
-
const
|
|
716
|
+
const baseMessage = buildProviderMessage(msg.message, attachments);
|
|
717
|
+
const fullMessage = preface ? `${preface}
|
|
718
|
+
|
|
719
|
+
${baseMessage}` : baseMessage;
|
|
720
|
+
console.log("[JobQueue] Running provider via PTY:", selection.providerId, selection.modelKey, isRetry ? "(retry)" : "");
|
|
721
|
+
const generation = procState.procGeneration;
|
|
722
|
+
const sessionId = buildTerminalSessionId(job.conversationId);
|
|
723
|
+
let session;
|
|
510
724
|
try {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
725
|
+
session = getOrCreateTerminalSession({
|
|
726
|
+
sessionId,
|
|
727
|
+
cwd: workingDirectory,
|
|
728
|
+
providerId: selection.providerId,
|
|
729
|
+
modelKey: selection.modelKey,
|
|
730
|
+
resumeSessionId,
|
|
731
|
+
hookContext: {
|
|
732
|
+
enabled: true,
|
|
733
|
+
providerId: selection.providerId,
|
|
734
|
+
conversationId: job.conversationId,
|
|
735
|
+
jobId: job.id,
|
|
736
|
+
requestId: msg.requestId
|
|
521
737
|
},
|
|
522
|
-
{
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
const completedToolInput = event.type === "block_start" || event.type === "block_delta" || event.type === "block_end" ? trackStreamingToolInput(event, activeToolInputs) : null;
|
|
528
|
-
if (event.sessionId) {
|
|
529
|
-
convState.providerSessionId = event.sessionId;
|
|
530
|
-
}
|
|
531
|
-
if (isRenderableEvent(event)) {
|
|
532
|
-
emittedRenderableContent = true;
|
|
533
|
-
}
|
|
534
|
-
if (event.type === "error" || event.type === "turn_result" && event.subtype !== "success") {
|
|
535
|
-
emittedTerminalErrorEvent = true;
|
|
536
|
-
}
|
|
537
|
-
if (completedToolInput && isUserInputToolName(completedToolInput.name)) {
|
|
538
|
-
permissionRequested = true;
|
|
539
|
-
this.setJobStatus(job, "done");
|
|
540
|
-
this.emitAndBuffer(job, { type: "ui_event", event });
|
|
541
|
-
this.emitAndBuffer(job, {
|
|
542
|
-
type: "done",
|
|
543
|
-
requestId: msg.requestId,
|
|
544
|
-
awaitingUserInput: true,
|
|
545
|
-
tool: completedToolInput.name,
|
|
546
|
-
input: parseToolInputJson(completedToolInput.inputJson)
|
|
547
|
-
});
|
|
548
|
-
(_a = procState.proc) == null ? void 0 : _a.kill();
|
|
549
|
-
return;
|
|
550
|
-
}
|
|
551
|
-
if ((mode === "ask" || mode === "plan") && !permissionRequested) {
|
|
552
|
-
const permRequest = deriveApprovalRequestFromEvent(
|
|
553
|
-
event,
|
|
554
|
-
convState.approvedTools,
|
|
555
|
-
selection.providerId,
|
|
556
|
-
mode
|
|
557
|
-
);
|
|
558
|
-
if (permRequest) {
|
|
559
|
-
permissionRequested = true;
|
|
560
|
-
procState.pendingTools = permRequest.tools || [permRequest.tool];
|
|
561
|
-
job.status = "waiting_permission";
|
|
562
|
-
this.emitAndBuffer(job, {
|
|
563
|
-
type: "permission_request",
|
|
564
|
-
tool: permRequest.tool,
|
|
565
|
-
tools: procState.pendingTools,
|
|
566
|
-
description: permRequest.description || `Permission required: ${permRequest.tool}`
|
|
567
|
-
});
|
|
568
|
-
(_b = procState.proc) == null ? void 0 : _b.kill();
|
|
569
|
-
return;
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
this.emitAndBuffer(job, { type: "ui_event", event });
|
|
573
|
-
}
|
|
574
|
-
},
|
|
575
|
-
onClose: ({ exitCode, signal, nonJsonOutput }) => {
|
|
576
|
-
if (procState.procGeneration !== generation) {
|
|
577
|
-
return;
|
|
578
|
-
}
|
|
579
|
-
try {
|
|
580
|
-
if (!permissionRequested) {
|
|
581
|
-
if (exitCode !== 0 && exitCode !== null) {
|
|
582
|
-
console.error("[JobQueue] Provider exited unexpectedly", {
|
|
583
|
-
providerId: selection.providerId,
|
|
584
|
-
modelKey: selection.modelKey,
|
|
585
|
-
permissionMode: mode,
|
|
586
|
-
requestId: msg.requestId,
|
|
587
|
-
exitCode,
|
|
588
|
-
signal,
|
|
589
|
-
nonJsonOutput: nonJsonOutput.slice(-25)
|
|
590
|
-
});
|
|
591
|
-
const inferred = deriveApprovalRequestFromProcessOutput(nonJsonOutput, mode);
|
|
592
|
-
if (inferred) {
|
|
593
|
-
permissionRequested = true;
|
|
594
|
-
procState.pendingTools = inferred.tools || [inferred.tool];
|
|
595
|
-
job.status = "waiting_permission";
|
|
596
|
-
this.emitAndBuffer(job, {
|
|
597
|
-
type: "permission_request",
|
|
598
|
-
tool: inferred.tool,
|
|
599
|
-
tools: procState.pendingTools,
|
|
600
|
-
description: inferred.description
|
|
601
|
-
});
|
|
602
|
-
procState.proc = null;
|
|
603
|
-
return;
|
|
604
|
-
}
|
|
605
|
-
const hasPermissionError = hasCodexPermissionError(nonJsonOutput);
|
|
606
|
-
const missingRolloutPath = hasCodexMissingRolloutPathError(nonJsonOutput);
|
|
607
|
-
if (missingRolloutPath && !hasPermissionError && !isRetry) {
|
|
608
|
-
this.emitAndBuffer(job, {
|
|
609
|
-
type: "session_reset",
|
|
610
|
-
reason: "Codex session state was missing rollout data. Retrying with a fresh ephemeral session."
|
|
611
|
-
});
|
|
612
|
-
convState.providerSessionId = null;
|
|
613
|
-
procState.proc = null;
|
|
614
|
-
this.runProvider(job, true, true);
|
|
615
|
-
return;
|
|
616
|
-
}
|
|
617
|
-
if (usedResumeFlag && !isRetry) {
|
|
618
|
-
const retryWithEphemeral = selection.providerId === "codex";
|
|
619
|
-
this.emitAndBuffer(job, {
|
|
620
|
-
type: "session_reset",
|
|
621
|
-
reason: retryWithEphemeral ? `Session resume failed (exit code ${exitCode}). Retrying with a fresh ephemeral session.` : `Session resume failed (exit code ${exitCode}). Retrying with a fresh session.`
|
|
622
|
-
});
|
|
623
|
-
convState.providerSessionId = null;
|
|
624
|
-
procState.proc = null;
|
|
625
|
-
this.runProvider(job, true, retryWithEphemeral);
|
|
626
|
-
return;
|
|
627
|
-
}
|
|
628
|
-
const summary = summarizeProviderProcessError(nonJsonOutput, 700);
|
|
629
|
-
if (!summary && emittedTerminalErrorEvent && emittedRenderableContent) {
|
|
630
|
-
this.emitAndBuffer(job, { type: "done", requestId: msg.requestId });
|
|
631
|
-
this.setJobStatus(job, "done");
|
|
632
|
-
return;
|
|
633
|
-
}
|
|
634
|
-
const details = summary ? ` \u2014 ${summary}` : "";
|
|
635
|
-
this.emitAndBuffer(job, {
|
|
636
|
-
type: "error",
|
|
637
|
-
error: `Provider process exited unexpectedly (code: ${exitCode}${signal ? ", signal: " + signal : ""})${details}`,
|
|
638
|
-
requestId: msg.requestId
|
|
639
|
-
});
|
|
640
|
-
this.setJobStatus(job, "error");
|
|
641
|
-
} else if (exitCode === null && signal) {
|
|
642
|
-
const summary = summarizeProviderProcessError(nonJsonOutput, 700);
|
|
643
|
-
const details = summary ? ` \u2014 ${summary}` : "";
|
|
644
|
-
this.emitAndBuffer(job, {
|
|
645
|
-
type: "error",
|
|
646
|
-
error: `Provider process was killed by signal ${signal}${details}`,
|
|
647
|
-
requestId: msg.requestId
|
|
648
|
-
});
|
|
649
|
-
this.setJobStatus(job, "error");
|
|
650
|
-
console.error("[JobQueue] Provider killed by signal", {
|
|
651
|
-
providerId: selection.providerId,
|
|
652
|
-
modelKey: selection.modelKey,
|
|
653
|
-
permissionMode: mode,
|
|
654
|
-
requestId: msg.requestId,
|
|
655
|
-
signal,
|
|
656
|
-
nonJsonOutput: nonJsonOutput.slice(-25)
|
|
657
|
-
});
|
|
658
|
-
} else {
|
|
659
|
-
if (!emittedRenderableContent) {
|
|
660
|
-
const summary = summarizeProviderProcessError(nonJsonOutput, 700);
|
|
661
|
-
const fallbackText = summary ? `Provider returned no structured response.
|
|
662
|
-
|
|
663
|
-
Raw output:
|
|
664
|
-
${summary}` : "Provider completed without returning visible response content.";
|
|
665
|
-
this.emitAssistantText(job, fallbackText, convState.providerSessionId);
|
|
666
|
-
}
|
|
667
|
-
this.emitAndBuffer(job, { type: "done", requestId: msg.requestId });
|
|
668
|
-
this.setJobStatus(job, "done");
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
} finally {
|
|
672
|
-
procState.proc = null;
|
|
673
|
-
}
|
|
674
|
-
},
|
|
675
|
-
onError: (error) => {
|
|
676
|
-
try {
|
|
677
|
-
this.emitAndBuffer(job, {
|
|
678
|
-
type: "error",
|
|
679
|
-
error: `Provider process error: ${error.message}`,
|
|
680
|
-
requestId: msg.requestId
|
|
681
|
-
});
|
|
682
|
-
this.setJobStatus(job, "error");
|
|
683
|
-
} finally {
|
|
684
|
-
procState.pendingTools = [];
|
|
685
|
-
procState.proc = null;
|
|
686
|
-
}
|
|
687
|
-
}
|
|
738
|
+
onProviderSessionId: (providerSessionId) => {
|
|
739
|
+
convState.providerSessionId = providerSessionId;
|
|
740
|
+
updateConversationProviderSessionInStorage(job.conversationId, providerSessionId).catch((error) => {
|
|
741
|
+
console.warn("[JobQueue] Failed to persist provider session id:", error);
|
|
742
|
+
});
|
|
688
743
|
}
|
|
689
|
-
);
|
|
744
|
+
});
|
|
690
745
|
} catch (error) {
|
|
691
|
-
const errorMsg = error instanceof Error ? error.message : "Failed to start provider process";
|
|
692
746
|
this.emitAndBuffer(job, {
|
|
693
747
|
type: "error",
|
|
694
|
-
error:
|
|
748
|
+
error: error instanceof Error ? error.message : "Failed to start provider terminal",
|
|
695
749
|
requestId: msg.requestId
|
|
696
750
|
});
|
|
697
751
|
this.setJobStatus(job, "error");
|
|
698
|
-
|
|
699
|
-
procState.proc = null;
|
|
752
|
+
return;
|
|
700
753
|
}
|
|
754
|
+
let finished = false;
|
|
755
|
+
let submitted = false;
|
|
756
|
+
let watchdog;
|
|
757
|
+
let idleCheck;
|
|
758
|
+
let unsubscribeExit;
|
|
759
|
+
let lastDataAt = Date.now();
|
|
760
|
+
let sawPostSubmitData = false;
|
|
761
|
+
const cleanup = () => {
|
|
762
|
+
monitor.stop();
|
|
763
|
+
if (watchdog) clearTimeout(watchdog);
|
|
764
|
+
if (idleCheck) clearInterval(idleCheck);
|
|
765
|
+
unsubscribeExit == null ? void 0 : unsubscribeExit();
|
|
766
|
+
};
|
|
767
|
+
const finalize = (lastAssistantMessage) => {
|
|
768
|
+
if (finished) return;
|
|
769
|
+
finished = true;
|
|
770
|
+
cleanup();
|
|
771
|
+
if (procState.procGeneration !== generation) return;
|
|
772
|
+
const text = lastAssistantMessage == null ? void 0 : lastAssistantMessage.trim();
|
|
773
|
+
if (text) this.emitAssistantText(job, text, convState.providerSessionId);
|
|
774
|
+
this.emitAndBuffer(job, { type: "cli_turn_stop_confirmed", requestId: msg.requestId });
|
|
775
|
+
this.emitAndBuffer(job, { type: "done", requestId: msg.requestId });
|
|
776
|
+
this.setJobStatus(job, "done");
|
|
777
|
+
procState.proc = null;
|
|
778
|
+
};
|
|
779
|
+
const finalizeError = (reason) => {
|
|
780
|
+
if (finished) return;
|
|
781
|
+
finished = true;
|
|
782
|
+
cleanup();
|
|
783
|
+
if (procState.procGeneration !== generation) return;
|
|
784
|
+
this.emitAndBuffer(job, { type: "error", error: reason, requestId: msg.requestId });
|
|
785
|
+
this.setJobStatus(job, "error");
|
|
786
|
+
procState.proc = null;
|
|
787
|
+
};
|
|
788
|
+
const monitor = startCliHookMonitor({
|
|
789
|
+
conversationId: job.conversationId,
|
|
790
|
+
emit: (event) => this.emitAndBuffer(job, event),
|
|
791
|
+
onStop: (lastAssistantMessage) => finalize(lastAssistantMessage),
|
|
792
|
+
isArmed: () => submitted,
|
|
793
|
+
// Only finalize on a Stop that follows this turn's own (post-submit)
|
|
794
|
+
// UserPromptSubmit, so a stray Stop from a superseded turn in the reused
|
|
795
|
+
// PTY can never be mistaken for completion. The output-idle watchdog below
|
|
796
|
+
// remains the guaranteed fallback if the prompt hook never lands.
|
|
797
|
+
requireArmedPromptSubmit: true
|
|
798
|
+
});
|
|
799
|
+
const handleExit = () => {
|
|
800
|
+
if (finished) return;
|
|
801
|
+
if (usedResumeFlag && !isRetry) {
|
|
802
|
+
finished = true;
|
|
803
|
+
cleanup();
|
|
804
|
+
if (procState.procGeneration !== generation) return;
|
|
805
|
+
convState.providerSessionId = null;
|
|
806
|
+
this.emitAndBuffer(job, { type: "session_reset", reason: "Resume failed; starting a fresh session" });
|
|
807
|
+
this.runProviderViaPty(job, true).catch((error) => {
|
|
808
|
+
this.emitAndBuffer(job, {
|
|
809
|
+
type: "error",
|
|
810
|
+
error: error instanceof Error ? error.message : "Provider retry failed",
|
|
811
|
+
requestId: msg.requestId
|
|
812
|
+
});
|
|
813
|
+
this.setJobStatus(job, "error");
|
|
814
|
+
});
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
finalizeError("Provider terminal exited before the turn completed");
|
|
818
|
+
};
|
|
819
|
+
unsubscribeExit = subscribeTerminalSession(session, {
|
|
820
|
+
onData: () => {
|
|
821
|
+
lastDataAt = Date.now();
|
|
822
|
+
if (submitted) sawPostSubmitData = true;
|
|
823
|
+
},
|
|
824
|
+
onExit: () => handleExit()
|
|
825
|
+
});
|
|
826
|
+
idleCheck = setInterval(() => {
|
|
827
|
+
if (finished) return;
|
|
828
|
+
if (!submitted || !sawPostSubmitData) return;
|
|
829
|
+
if (Date.now() - lastDataAt >= PTY_IDLE_FINALIZE_MS) {
|
|
830
|
+
console.warn("[JobQueue] PTY turn completed via output-idle fallback (no Stop hook).");
|
|
831
|
+
finalize();
|
|
832
|
+
}
|
|
833
|
+
}, 500);
|
|
834
|
+
watchdog = setTimeout(() => finalizeError("Provider turn timed out"), PTY_TURN_TIMEOUT_MS);
|
|
835
|
+
procState.proc = {
|
|
836
|
+
kill: () => {
|
|
837
|
+
try {
|
|
838
|
+
writeTerminalInput(sessionId, "");
|
|
839
|
+
} catch {
|
|
840
|
+
}
|
|
841
|
+
cleanup();
|
|
842
|
+
}
|
|
843
|
+
};
|
|
844
|
+
this.injectMessage(
|
|
845
|
+
session,
|
|
846
|
+
sessionId,
|
|
847
|
+
fullMessage,
|
|
848
|
+
() => procState.procGeneration === generation && !finished,
|
|
849
|
+
() => {
|
|
850
|
+
submitted = true;
|
|
851
|
+
}
|
|
852
|
+
);
|
|
701
853
|
}
|
|
702
854
|
emitAssistantText(job, text, sessionId) {
|
|
703
855
|
const blockId = `blk-${Date.now()}`;
|