claude-code-session-manager 0.61.0 → 0.62.1
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/assets/{TiptapBody-CCmz3PdX.js → TiptapBody-BwN4pNC0.js} +42 -42
- package/dist/assets/{index-CPMP2XZ_.css → index-CVCCMw5o.css} +1 -1
- package/dist/assets/index-ZjTDk5b7.js +3197 -0
- package/dist/index.html +2 -2
- package/package.json +4 -2
- package/src/main/__tests__/chat-preamble-anchors.test.cjs +100 -0
- package/src/main/__tests__/runVerify-transcript-commit-evidence.test.cjs +215 -0
- package/src/main/__tests__/scheduler-investigation-prompt.test.cjs +42 -1
- package/src/main/__tests__/scheduler-prd-missing-skip.test.cjs +54 -1
- package/src/main/__tests__/transcripts-doFlush-array.test.cjs +6 -0
- package/src/main/__tests__/transcripts-paged-reads.test.cjs +6 -0
- package/src/main/__tests__/transcriptsUsageFor.test.cjs +6 -0
- package/src/main/chatRunner.cjs +4 -0
- package/src/main/config.cjs +30 -23
- package/src/main/files.cjs +20 -0
- package/src/main/index.cjs +1 -63
- package/src/main/ipcSchemas.cjs +11 -209
- package/src/main/lib/__tests__/coldBootPromptSessionsWrite.test.cjs +87 -0
- package/src/main/lib/activeIndexMerge.cjs +9 -0
- package/src/main/lib/opsOwnership.cjs +0 -2
- package/src/main/lib/summarize.cjs +0 -1
- package/src/main/runVerify.cjs +55 -6
- package/src/main/scheduler.cjs +46 -7
- package/src/preload/api.d.ts +2 -197
- package/src/preload/index.cjs +1 -100
- package/dist/assets/index-B5994QOJ.js +0 -3222
- package/src/main/__tests__/browserAgentServer.test.cjs +0 -235
- package/src/main/__tests__/browserView-destroyed-handler.test.cjs +0 -85
- package/src/main/__tests__/browserView-oauth-popup.test.cjs +0 -126
- package/src/main/__tests__/web-remote-e2e-pinning.test.cjs +0 -181
- package/src/main/browserAgentServer.cjs +0 -224
- package/src/main/browserCapture.cjs +0 -728
- package/src/main/browserView.cjs +0 -916
- package/src/main/lib/__tests__/browserAgentActions.test.cjs +0 -76
- package/src/main/lib/browserAgentActions.cjs +0 -114
- package/src/main/lib/e2eStateMachine.cjs +0 -39
- package/src/main/webRemote.cjs +0 -1367
- package/src/preload/browserViewPreload.cjs +0 -118
package/src/main/runVerify.cjs
CHANGED
|
@@ -157,6 +157,7 @@ function parseLog(logPath) {
|
|
|
157
157
|
const events = [];
|
|
158
158
|
let resultEvent = null;
|
|
159
159
|
let seq = 0;
|
|
160
|
+
let transcriptCommitLanded = false;
|
|
160
161
|
|
|
161
162
|
for (const line of text.split('\n')) {
|
|
162
163
|
const trimmed = line.trim();
|
|
@@ -166,6 +167,18 @@ function parseLog(logPath) {
|
|
|
166
167
|
try { obj = JSON.parse(trimmed); } catch { continue; }
|
|
167
168
|
if (!obj || typeof obj !== 'object') continue;
|
|
168
169
|
|
|
170
|
+
// Harness-emitted commit evidence: git itself produced these lines (a
|
|
171
|
+
// `vcs_state_changed` system event, or a tool_use_result carrying a
|
|
172
|
+
// gitOperation), so the model cannot fabricate them. Detected here as a
|
|
173
|
+
// plain boolean scan — never pushed into `events`, so the index-based
|
|
174
|
+
// final-20% math elsewhere is unaffected.
|
|
175
|
+
if (obj.type === 'system' && obj.subtype === 'vcs_state_changed' && obj.kind === 'commit') {
|
|
176
|
+
transcriptCommitLanded = true;
|
|
177
|
+
}
|
|
178
|
+
if (obj.tool_use_result?.gitOperation?.commit?.kind === 'committed') {
|
|
179
|
+
transcriptCommitLanded = true;
|
|
180
|
+
}
|
|
181
|
+
|
|
169
182
|
// Final result event.
|
|
170
183
|
if (obj.type === 'result') {
|
|
171
184
|
const ev = {
|
|
@@ -215,7 +228,7 @@ function parseLog(logPath) {
|
|
|
215
228
|
}
|
|
216
229
|
}
|
|
217
230
|
|
|
218
|
-
return { events, resultEvent, error: null };
|
|
231
|
+
return { events, resultEvent, error: null, transcriptCommitLanded };
|
|
219
232
|
}
|
|
220
233
|
|
|
221
234
|
// ─── self-recovery helpers ────────────────────────────────────────────────────
|
|
@@ -591,6 +604,27 @@ function isAncestorCommit({ cwd, sha, timeoutMs = GH_CHECK_TIMEOUT_MS, execImpl
|
|
|
591
604
|
}
|
|
592
605
|
}
|
|
593
606
|
|
|
607
|
+
/**
|
|
608
|
+
* True when `cwd` is inside a git repository. Used to gate the transcript
|
|
609
|
+
* commit-evidence fallback below: a job whose cwd is not a git repo can never
|
|
610
|
+
* produce committedDuringRun=true (gitHead()/committedInWindow() fail closed
|
|
611
|
+
* there), so the fallback must only apply in that narrow case — never when
|
|
612
|
+
* git ancestry is actually observable.
|
|
613
|
+
*/
|
|
614
|
+
function isGitRepo(cwd) {
|
|
615
|
+
if (!cwd) return false;
|
|
616
|
+
try {
|
|
617
|
+
execFileSync('git', ['-C', cwd, 'rev-parse', '--git-dir'], {
|
|
618
|
+
timeout: 10_000,
|
|
619
|
+
encoding: 'utf8',
|
|
620
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
621
|
+
});
|
|
622
|
+
return true;
|
|
623
|
+
} catch {
|
|
624
|
+
return false;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
594
628
|
// ─── already-shipped postcondition exemption (original PRD re-runs) ─────────
|
|
595
629
|
|
|
596
630
|
const PRD_DELIVERABLE_PATH_RE = /(?:^|[`\s(])((?:src|scripts|session-manager-operations|test|tests|docs|bin)\/[A-Za-z0-9_./-]*[A-Za-z0-9_-]\.[A-Za-z0-9]+)\b/g;
|
|
@@ -743,12 +777,21 @@ async function verifyRun({ runDir, prdPath, queueEntry, allJobs = [], committedD
|
|
|
743
777
|
}
|
|
744
778
|
|
|
745
779
|
// ── 2. Parse log ───────────────────────────────────────────────────────
|
|
746
|
-
const { events, resultEvent, error: parseError } = parseLog(logPath);
|
|
780
|
+
const { events, resultEvent, error: parseError, transcriptCommitLanded } = parseLog(logPath);
|
|
747
781
|
|
|
748
782
|
if (parseError) {
|
|
749
783
|
return conclude('verify_unavailable', `log unreadable: ${parseError}`, 'needs_review');
|
|
750
784
|
}
|
|
751
785
|
|
|
786
|
+
// A job whose cwd is not a git repo (e.g. an investigation fix-plan that
|
|
787
|
+
// inherited cwd: /tmp) can never produce committedDuringRun=true —
|
|
788
|
+
// gitHead()/committedInWindow() both fail closed there. In that case
|
|
789
|
+
// ONLY, fall back to the harness-emitted commit evidence in the
|
|
790
|
+
// transcript, which git itself produced and the model cannot fabricate.
|
|
791
|
+
const cwdIsGitRepo = isGitRepo(queueEntry?.cwd);
|
|
792
|
+
const commitEvidence = committedDuringRun || (!cwdIsGitRepo && transcriptCommitLanded);
|
|
793
|
+
const commitEvidenceSource = (!committedDuringRun && commitEvidence) ? 'transcript' : null;
|
|
794
|
+
|
|
752
795
|
// ── 3. HALT detection ─────────────────────────────────────────────────
|
|
753
796
|
// Primary: check the final `{"type":"result"}` event's `result` text.
|
|
754
797
|
if (resultEvent) {
|
|
@@ -865,7 +908,7 @@ async function verifyRun({ runDir, prdPath, queueEntry, allJobs = [], committedD
|
|
|
865
908
|
// committed anything nor ever emitted the finish-protocol sentinel likely
|
|
866
909
|
// ended before doing real work (e.g. stopped on a clarifying question).
|
|
867
910
|
// Weaker evidence than a caught transcript error, but still not clean.
|
|
868
|
-
if (sentinel === null && !
|
|
911
|
+
if (sentinel === null && !commitEvidence) {
|
|
869
912
|
issues.push({
|
|
870
913
|
verdict: 'no_verdict_sentinel',
|
|
871
914
|
reason: 'run made no commit and emitted no SCHEDULER_VERDICT sentinel — likely ended before the finish protocol (e.g. stopped on a clarifying question)',
|
|
@@ -894,7 +937,7 @@ async function verifyRun({ runDir, prdPath, queueEntry, allJobs = [], committedD
|
|
|
894
937
|
// bare PASS with no commit from one is still a strong silent-failure
|
|
895
938
|
// signal and stays covered by this check.
|
|
896
939
|
const isFixPlanJob = /^\d+-fix-/.test(queueEntry?.slug || '');
|
|
897
|
-
if (sentinel === 'pass' && !
|
|
940
|
+
if (sentinel === 'pass' && !commitEvidence && !isFixPlanJob) {
|
|
898
941
|
// EXEMPTION: `-merge-main` PRDs (see isMergeMainSlug) can genuinely and
|
|
899
942
|
// correctly find nothing left to do when an out-of-band actor (a human,
|
|
900
943
|
// another agent, a sibling scheduler job) already merged/updated the
|
|
@@ -1015,13 +1058,18 @@ async function verifyRun({ runDir, prdPath, queueEntry, allJobs = [], committedD
|
|
|
1015
1058
|
// MUST NOT apply to halt or deps_unmet — those keep their existing semantics.
|
|
1016
1059
|
if (
|
|
1017
1060
|
sentinel === 'pass'
|
|
1018
|
-
&&
|
|
1061
|
+
&& commitEvidence
|
|
1019
1062
|
&& (top.verdict === 'transcript_errors' || top.verdict === 'verify_unavailable')
|
|
1020
1063
|
) {
|
|
1021
1064
|
return conclude('clean',
|
|
1022
1065
|
`SCHEDULER_VERDICT: PASS + commit landed overrides ${top.verdict}`,
|
|
1023
1066
|
null,
|
|
1024
|
-
{
|
|
1067
|
+
{
|
|
1068
|
+
...(annotations.length ? { annotations } : {}),
|
|
1069
|
+
sentinel,
|
|
1070
|
+
sentinelOverride: top.verdict,
|
|
1071
|
+
...(commitEvidenceSource ? { commitEvidenceSource } : {}),
|
|
1072
|
+
},
|
|
1025
1073
|
);
|
|
1026
1074
|
}
|
|
1027
1075
|
|
|
@@ -1073,4 +1121,5 @@ module.exports = {
|
|
|
1073
1121
|
extractPrdDeliverablePaths,
|
|
1074
1122
|
allDeliverablesAlreadyTracked,
|
|
1075
1123
|
isAncestorCommit,
|
|
1124
|
+
isGitRepo,
|
|
1076
1125
|
};
|
package/src/main/scheduler.cjs
CHANGED
|
@@ -271,6 +271,28 @@ function gitHead(cwd) {
|
|
|
271
271
|
});
|
|
272
272
|
}
|
|
273
273
|
|
|
274
|
+
// True when cwd is inside a git repository. Used to keep a non-git cwd (e.g.
|
|
275
|
+
// a scratch dir like /tmp) from ever being handed to an investigation's
|
|
276
|
+
// fix-plan as its cwd — the commit guard, worktree isolation, and
|
|
277
|
+
// runVerify's PASS+commit sentinel override all read git state from job.cwd,
|
|
278
|
+
// so a fix plan rooted at a non-repo path gets parked in needs_review even
|
|
279
|
+
// when its commit really landed (Incident: 99-fix-e2e-needs-review-test,
|
|
280
|
+
// 2026-08-07 — green, committed 6ecbd40, PASS sentinel, still needs_review).
|
|
281
|
+
// Bounded and never throws.
|
|
282
|
+
function isGitRepoSync(cwd) {
|
|
283
|
+
if (!cwd) return false;
|
|
284
|
+
try {
|
|
285
|
+
execFileSync('git', ['-C', cwd, 'rev-parse', '--git-dir'], {
|
|
286
|
+
timeout: 10_000,
|
|
287
|
+
windowsHide: true,
|
|
288
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
289
|
+
});
|
|
290
|
+
return true;
|
|
291
|
+
} catch {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
274
296
|
// Best-effort `git fetch --all --prune` in cwd, bounded and never throwing.
|
|
275
297
|
// A PRD that does its work in a separate `git worktree add` checkout (per
|
|
276
298
|
// standards.md's own recommended pattern for shared repos) commits and pushes
|
|
@@ -2666,6 +2688,10 @@ ${logTail}
|
|
|
2666
2688
|
estimateMinutes: <your time estimate>
|
|
2667
2689
|
---
|
|
2668
2690
|
\`\`\`
|
|
2691
|
+
\`cwd\` must be the git repo root where the fix will actually land. If the failed job's cwd is
|
|
2692
|
+
not that repo (e.g. a scratch dir like \`/tmp\`), set \`cwd:\` to the correct repo root instead —
|
|
2693
|
+
the scheduler's commit guard and post-run verifier read git state from this path, and a
|
|
2694
|
+
non-repo cwd parks even a fully green, committed run in \`needs_review\`.
|
|
2669
2695
|
6. The PRD body MUST be self-contained — \`claude -p\` runs it on a fresh Sonnet session with NO conversation context. Include:
|
|
2670
2696
|
- Root-cause analysis (what went wrong and why)
|
|
2671
2697
|
- Concrete fix steps (specific files / commands / edits)
|
|
@@ -2803,6 +2829,16 @@ async function spawnInvestigation(failedJob, runDir) {
|
|
|
2803
2829
|
console.warn(`[scheduler] investigation cwd missing (${cwd}); falling back to ${DEFAULT_PROJECT_CWD}`);
|
|
2804
2830
|
cwd = DEFAULT_PROJECT_CWD;
|
|
2805
2831
|
}
|
|
2832
|
+
// A non-git cwd is unusable as a fix-plan cwd: the commit guard, worktree
|
|
2833
|
+
// isolation, and runVerify's PASS+commit sentinel override all read git
|
|
2834
|
+
// state from job.cwd, so a fix plan rooted at e.g. /tmp gets parked in
|
|
2835
|
+
// needs_review even when its commit really landed. (Incident:
|
|
2836
|
+
// 99-fix-e2e-needs-review-test, 2026-08-07 — green, committed 6ecbd40,
|
|
2837
|
+
// PASS sentinel, still needs_review.)
|
|
2838
|
+
if (!isGitRepoSync(cwd)) {
|
|
2839
|
+
console.warn(`[scheduler] investigation cwd is not a git repo (${cwd}); falling back to ${DEFAULT_PROJECT_CWD}`);
|
|
2840
|
+
cwd = DEFAULT_PROJECT_CWD;
|
|
2841
|
+
}
|
|
2806
2842
|
const prompt = buildInvestigationPrompt({ failedJob, cwd, failedLogPath, originalBody, logTail, fixPath, group });
|
|
2807
2843
|
|
|
2808
2844
|
// Phase 1: open log fd for pre-spawn diagnostics.
|
|
@@ -3024,11 +3060,12 @@ async function spawnJob(job, runId, runDir, defaultCwd) {
|
|
|
3024
3060
|
await setPaused('rate_limit', resetIso);
|
|
3025
3061
|
}
|
|
3026
3062
|
|
|
3027
|
-
// Stale queue entry: the PRD already shipped
|
|
3028
|
-
//
|
|
3029
|
-
//
|
|
3030
|
-
//
|
|
3031
|
-
|
|
3063
|
+
// Stale queue entry: the PRD was archived (already shipped) or is gone
|
|
3064
|
+
// from disk entirely (deleted before dispatch). Both are retire-not-fail
|
|
3065
|
+
// results from prdArchivedSkipResult with exitCode 0 and no transcript —
|
|
3066
|
+
// treat any truthy skip reason as a plain completion: no verify pass, no
|
|
3067
|
+
// commit guard, no RCA feedback item.
|
|
3068
|
+
if (res.skipped) {
|
|
3032
3069
|
await mutate((s) => {
|
|
3033
3070
|
const idx = s.jobs.findIndex((x) => x.slug === job.slug);
|
|
3034
3071
|
if (idx >= 0) {
|
|
@@ -4765,7 +4802,9 @@ async function init() {
|
|
|
4765
4802
|
}
|
|
4766
4803
|
}
|
|
4767
4804
|
|
|
4768
|
-
// remote —
|
|
4805
|
+
// remote — in-process (non-IPC) scheduler accessors, used by prdCreate.cjs
|
|
4806
|
+
// and other main-process callers. (Named for the retired web-remote relay,
|
|
4807
|
+
// its original consumer; kept because it still has in-process callers.)
|
|
4769
4808
|
const remote = {
|
|
4770
4809
|
// `cwd` is optional: prdCreate.cjs's create flow passes the target
|
|
4771
4810
|
// project's cwd explicitly (the file may not exist yet, so there's
|
|
@@ -4956,4 +4995,4 @@ function registerAdminRoutes(adminHttp, remoteObj = remote) {
|
|
|
4956
4995
|
});
|
|
4957
4996
|
}
|
|
4958
4997
|
|
|
4959
|
-
module.exports = { registerScheduleHandlers, attachWindow, init, ROOT, PRDS_DIR, healRefusalReason, writeQueue, reconcile, reconcileSourcePromptId, allocateParallelGroup, selectHistoryJobs, parsePorcelain, FINISH_PROTOCOL, remote, pickNextBatch, pickForProject, reapDeadRunningJobs, pollRecoveryClearSource, memoryLimitedBatchSize, availableForJobs, reverifyNeedsReview, isRescanCandidate, isPromotableOriginal, selectAutoFixTargets, isEligibleForImmediateAutoFix, resolveRunId, isUnresolvableNeedsReview, healTargetForFix, buildInvestigationPrompt, committedInWindow, computeCommittedDuringRun, classifySigtermWithCommit, isFixPlanSlug, isFixPlanBeyondDepthCap, MAX_INVESTIGATION_DEPTH, forceTickOutcome, applyPauseCleared, detectNetworkErrorInLog, detectRateLimitInLog, classifyFailureOutcome, commitGuardVerdict, TRANSIENT_RETRY_CAP, buildScheduleStatePayload, partitionBootOrphans, applyOrphanOutcome, BOOT_ORPHAN_KILL_GRACE_MS, registerAdminRoutes, notifyOriginatingTab, notifyNeedsReview, isNotifiableTerminalStatus, extractResultTextFromLog, candidatePrdsDirs, candidateArchivedPrdsDirs, resolveArchivedPrdStatus, prdDirForCwd, prdPathForJob, archivedPrdPathForJob, archivedTwinExists, findPrdDir, resolveVerifyPrdPath, resolveNotifyPrd, runPrdMigration, shouldSkipInvestigationForCleanRun, archiveCompletedPrd, retireCompletedSlugs, SCHEDULER_BOOTED_AT, SCHEDULER_CODE_SHA, resetJobFields, executeJob, prdArchivedSkipResult };
|
|
4998
|
+
module.exports = { registerScheduleHandlers, attachWindow, init, ROOT, PRDS_DIR, healRefusalReason, writeQueue, reconcile, reconcileSourcePromptId, allocateParallelGroup, selectHistoryJobs, parsePorcelain, FINISH_PROTOCOL, remote, pickNextBatch, pickForProject, reapDeadRunningJobs, pollRecoveryClearSource, memoryLimitedBatchSize, availableForJobs, reverifyNeedsReview, isRescanCandidate, isPromotableOriginal, selectAutoFixTargets, isEligibleForImmediateAutoFix, resolveRunId, isUnresolvableNeedsReview, healTargetForFix, buildInvestigationPrompt, isGitRepoSync, committedInWindow, computeCommittedDuringRun, classifySigtermWithCommit, isFixPlanSlug, isFixPlanBeyondDepthCap, MAX_INVESTIGATION_DEPTH, forceTickOutcome, applyPauseCleared, detectNetworkErrorInLog, detectRateLimitInLog, classifyFailureOutcome, commitGuardVerdict, TRANSIENT_RETRY_CAP, buildScheduleStatePayload, partitionBootOrphans, applyOrphanOutcome, BOOT_ORPHAN_KILL_GRACE_MS, registerAdminRoutes, notifyOriginatingTab, notifyNeedsReview, isNotifiableTerminalStatus, extractResultTextFromLog, candidatePrdsDirs, candidateArchivedPrdsDirs, resolveArchivedPrdStatus, prdDirForCwd, prdPathForJob, archivedPrdPathForJob, archivedTwinExists, findPrdDir, resolveVerifyPrdPath, resolveNotifyPrd, runPrdMigration, shouldSkipInvestigationForCleanRun, archiveCompletedPrd, retireCompletedSlugs, SCHEDULER_BOOTED_AT, SCHEDULER_CODE_SHA, resetJobFields, executeJob, prdArchivedSkipResult, spawnJob };
|
package/src/preload/api.d.ts
CHANGED
|
@@ -29,82 +29,6 @@ export interface WriteErrorEvent {
|
|
|
29
29
|
reason: string;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
export interface BrowserNavState {
|
|
33
|
-
url: string;
|
|
34
|
-
title: string;
|
|
35
|
-
canGoBack: boolean;
|
|
36
|
-
canGoForward: boolean;
|
|
37
|
-
loading: boolean;
|
|
38
|
-
isSecure: boolean;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** One captured recorder step (PRD 408 engine → PRD 409 panel). */
|
|
42
|
-
export interface RecordStep {
|
|
43
|
-
n: number;
|
|
44
|
-
/** `select` is accepted by replay/export (PRD 410) for forward-compat;
|
|
45
|
-
* the live engine does not emit it yet. */
|
|
46
|
-
verb: 'navigate' | 'click' | 'type' | 'select' | 'wait-for' | 'drag';
|
|
47
|
-
target: string;
|
|
48
|
-
kind?: 'nav' | 'assert';
|
|
49
|
-
/** True for `type` steps — the actual typed value is never captured. */
|
|
50
|
-
masked?: boolean;
|
|
51
|
-
/** Engine-suggested `{{var}}` name for a `type` step (e.g. field name). */
|
|
52
|
-
variableSuggestion?: string;
|
|
53
|
-
/** Renderer-owned: set once the user checks "parameterize as {{var}}". */
|
|
54
|
-
variable?: string | null;
|
|
55
|
-
/** `select` steps only — the option value to choose on replay/export. */
|
|
56
|
-
value?: string;
|
|
57
|
-
/** Click position, or drag-start position for `drag` steps. */
|
|
58
|
-
x?: number;
|
|
59
|
-
/** Click position, or drag-start position for `drag` steps. */
|
|
60
|
-
y?: number;
|
|
61
|
-
/** `drag` steps only — drag-end target selector. */
|
|
62
|
-
endTarget?: string;
|
|
63
|
-
/** `drag` steps only — drag-end x position. */
|
|
64
|
-
endX?: number;
|
|
65
|
-
/** `drag` steps only — drag-end y position. */
|
|
66
|
-
endY?: number;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/** Per-step replay outcome (PRD 410), streamed as `browser:replay-step:<viewId>`. */
|
|
70
|
-
export interface ReplayStepResult {
|
|
71
|
-
n: number;
|
|
72
|
-
status: 'pass' | 'fail';
|
|
73
|
-
detail?: string;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/** find-in-page result (PRD 402), streamed as `browser:find-result:<viewId>`. */
|
|
77
|
-
export interface FindResult {
|
|
78
|
-
requestId: number;
|
|
79
|
-
matches: number;
|
|
80
|
-
activeMatchOrdinal: number;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/** Element-picker event (PRD 403), streamed as `browser:picker-event:<viewId>`. */
|
|
84
|
-
export interface PickerEvent {
|
|
85
|
-
type: 'hover' | 'pick' | 'unpick' | 'exit';
|
|
86
|
-
selector?: string;
|
|
87
|
-
label?: string;
|
|
88
|
-
tag?: string;
|
|
89
|
-
rect?: { x: number; y: number; width: number; height: number };
|
|
90
|
-
/** Present on a 'pick' event fired by an unmodified click — the selection was replaced, not accumulated. */
|
|
91
|
-
replace?: boolean;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** One entry in `~/.claude/session-manager/browser/history.json` (PRD 402). */
|
|
95
|
-
export interface BrowserHistoryEntry {
|
|
96
|
-
url: string;
|
|
97
|
-
title: string;
|
|
98
|
-
ts: number;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/** One entry in `~/.claude/session-manager/browser/bookmarks.json` (PRD 402). */
|
|
102
|
-
export interface BrowserBookmark {
|
|
103
|
-
url: string;
|
|
104
|
-
title: string;
|
|
105
|
-
ts: number;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
32
|
export interface ReadJsonResult {
|
|
109
33
|
exists: boolean;
|
|
110
34
|
raw: string;
|
|
@@ -133,7 +57,7 @@ export interface ImportRef {
|
|
|
133
57
|
/** Declared owner ids for the single-writer law over a project's
|
|
134
58
|
* session-manager-operations/ root. Mirrors OWNERS in
|
|
135
59
|
* src/main/lib/opsOwnership.cjs — one writer per namespace; everyone reads. */
|
|
136
|
-
export type OpsWriter = 'epics' | 'scheduler' | 'project-home'
|
|
60
|
+
export type OpsWriter = 'epics' | 'scheduler' | 'project-home';
|
|
137
61
|
|
|
138
62
|
export interface WriteResult {
|
|
139
63
|
ok: boolean;
|
|
@@ -1162,46 +1086,6 @@ export interface PluginInstallProgressEvent {
|
|
|
1162
1086
|
line: string;
|
|
1163
1087
|
}
|
|
1164
1088
|
|
|
1165
|
-
// ────────────────────────────────────────────── Web Remote (PRD 08)
|
|
1166
|
-
|
|
1167
|
-
export interface WebRemoteDevice {
|
|
1168
|
-
deviceId: string;
|
|
1169
|
-
deviceName: string;
|
|
1170
|
-
issuedAt: string;
|
|
1171
|
-
lastConnectedAt: string | null;
|
|
1172
|
-
}
|
|
1173
|
-
|
|
1174
|
-
export interface WebRemoteStatus {
|
|
1175
|
-
enabled: boolean;
|
|
1176
|
-
remoteControlEnabled: boolean;
|
|
1177
|
-
connected: boolean;
|
|
1178
|
-
e2eActive: boolean;
|
|
1179
|
-
/** True once the user has confirmed the SAS on the desktop. */
|
|
1180
|
-
e2eAuthenticated: boolean;
|
|
1181
|
-
/** Explicit E2E session state for UI rendering. */
|
|
1182
|
-
e2eState: 'idle' | 'pending_sas' | 'authenticated' | 'failed';
|
|
1183
|
-
/** 6-digit Short Authentication String pending user confirmation, or null. */
|
|
1184
|
-
pendingSas: string | null;
|
|
1185
|
-
devices: WebRemoteDevice[];
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
export interface WebRemotePairResult {
|
|
1189
|
-
ok: boolean;
|
|
1190
|
-
deviceId?: string;
|
|
1191
|
-
error?: string;
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
|
-
export interface WebRemoteMutationResult {
|
|
1195
|
-
ok: boolean;
|
|
1196
|
-
error?: string;
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
export interface WebRemoteAuditTailResult {
|
|
1200
|
-
ok: boolean;
|
|
1201
|
-
lines?: string[];
|
|
1202
|
-
error?: string;
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
1089
|
// ────────────────────────────────────────────── Chat runner (PRD 319)
|
|
1206
1090
|
|
|
1207
1091
|
export interface ChatRunPayload {
|
|
@@ -1344,63 +1228,6 @@ export interface SessionManagerAPI {
|
|
|
1344
1228
|
onExit: (tabId: string, handler: (info: PtyExit) => void) => () => void;
|
|
1345
1229
|
onWriteError: (handler: (ev: WriteErrorEvent) => void) => () => void;
|
|
1346
1230
|
};
|
|
1347
|
-
browser: {
|
|
1348
|
-
create: (payload: { viewId: string; partition: string }) => Promise<{ ok: boolean }>;
|
|
1349
|
-
setBounds: (payload: { viewId: string; x: number; y: number; width: number; height: number }) => Promise<{ ok: boolean }>;
|
|
1350
|
-
show: (viewId: string) => Promise<{ ok: boolean }>;
|
|
1351
|
-
hide: (viewId: string) => Promise<{ ok: boolean }>;
|
|
1352
|
-
destroy: (viewId: string) => Promise<{ ok: boolean }>;
|
|
1353
|
-
navigate: (payload: { viewId: string; url: string }) => Promise<{ ok: boolean; error?: string }>;
|
|
1354
|
-
back: (viewId: string) => Promise<{ ok: boolean; error?: string }>;
|
|
1355
|
-
forward: (viewId: string) => Promise<{ ok: boolean; error?: string }>;
|
|
1356
|
-
reload: (viewId: string) => Promise<{ ok: boolean; error?: string }>;
|
|
1357
|
-
stop: (viewId: string) => Promise<{ ok: boolean; error?: string }>;
|
|
1358
|
-
onNavState: (viewId: string, handler: (state: BrowserNavState) => void) => () => void;
|
|
1359
|
-
recordStart: (viewId: string) => Promise<{ ok: boolean; error?: string }>;
|
|
1360
|
-
recordStop: (viewId: string) => Promise<{ ok: boolean; error?: string }>;
|
|
1361
|
-
onRecordStep: (viewId: string, handler: (step: RecordStep) => void) => () => void;
|
|
1362
|
-
captureDom: (payload: { viewId: string; kind: 'text' | 'html' }) => Promise<
|
|
1363
|
-
| { ok: true; url: string; title: string; text: string; truncated?: boolean }
|
|
1364
|
-
| { ok: false; error: string }
|
|
1365
|
-
>;
|
|
1366
|
-
captureShot: (viewId: string) => Promise<
|
|
1367
|
-
| { ok: true; url: string; title: string; dataUrl: string }
|
|
1368
|
-
| { ok: false; error: string }
|
|
1369
|
-
>;
|
|
1370
|
-
saveBinary: (path: string, base64: string, writer?: OpsWriter) => Promise<{ ok: boolean; error?: string }>;
|
|
1371
|
-
/** Opens a native "Save As" dialog and writes `text` to the chosen path directly
|
|
1372
|
-
* (bypasses config.cjs's write-boundary check — the path is user-chosen via OS dialog). */
|
|
1373
|
-
saveRecording: (payload: { defaultName: string; text: string }) => Promise<
|
|
1374
|
-
| { ok: true; path: string }
|
|
1375
|
-
| { ok: false; canceled: true }
|
|
1376
|
-
| { ok: false; error: string }
|
|
1377
|
-
>;
|
|
1378
|
-
replay: (payload: {
|
|
1379
|
-
viewId: string;
|
|
1380
|
-
steps: RecordStep[];
|
|
1381
|
-
values?: Record<string, string>;
|
|
1382
|
-
continueOnError?: boolean;
|
|
1383
|
-
}) => Promise<{ ok: boolean; error?: string; stopped?: boolean; failedAt?: number }>;
|
|
1384
|
-
onReplayStep: (viewId: string, handler: (step: ReplayStepResult) => void) => () => void;
|
|
1385
|
-
setZoom: (payload: { viewId: string; factor: number }) => Promise<{ ok: boolean; error?: string; factor?: number }>;
|
|
1386
|
-
find: (payload: { viewId: string; text: string; forward?: boolean }) => Promise<{ ok: boolean; error?: string }>;
|
|
1387
|
-
stopFind: (viewId: string) => Promise<{ ok: boolean; error?: string }>;
|
|
1388
|
-
onFindResult: (viewId: string, handler: (result: FindResult) => void) => () => void;
|
|
1389
|
-
pickerStart: (viewId: string) => Promise<{ ok: boolean; error?: string }>;
|
|
1390
|
-
pickerStop: (viewId: string) => Promise<{ ok: boolean; error?: string; wasPicking?: boolean }>;
|
|
1391
|
-
onPickerEvent: (viewId: string, handler: (ev: PickerEvent) => void) => () => void;
|
|
1392
|
-
/** Selection-scoped capture pipeline (PRD 404): filter -> prune -> summarize -> chunk for 'agent', outerHTML for 'html', CDP AX-tree for 'a11y', fallback-chain for 'selector'. */
|
|
1393
|
-
capture: (payload: {
|
|
1394
|
-
viewId: string;
|
|
1395
|
-
selectors: string[];
|
|
1396
|
-
mode: 'agent' | 'html' | 'a11y' | 'selector';
|
|
1397
|
-
}) => Promise<
|
|
1398
|
-
| { ok: true; mode: string; text: string; meta: { chunks?: number; tokens?: number } }
|
|
1399
|
-
| { ok: false; error: string }
|
|
1400
|
-
>;
|
|
1401
|
-
/** Fired when the embedded page tries to open a new window (target="_blank", window.open, ctrl/cmd-click) — the main process denies the native popup and forwards the URL here. */
|
|
1402
|
-
onOpenTabRequest: (handler: (payload: { url: string }) => void) => () => void;
|
|
1403
|
-
};
|
|
1404
1231
|
transcripts: {
|
|
1405
1232
|
subscribe: (payload: { tabId: string; cwd: string; sessionUuid: string }) => Promise<SubscribeResult>;
|
|
1406
1233
|
/** Release the sub back to the LRU cache (view-switch). Does not destroy the watcher. */
|
|
@@ -1507,6 +1334,7 @@ export interface SessionManagerAPI {
|
|
|
1507
1334
|
rename: (path: string, newName: string) => Promise<FilesRenameResult>;
|
|
1508
1335
|
delete: (path: string) => Promise<FilesDeleteResult>;
|
|
1509
1336
|
duplicate: (path: string) => Promise<FilesDuplicateResult>;
|
|
1337
|
+
saveBinary: (path: string, base64: string, writer?: OpsWriter) => Promise<{ ok: boolean; error?: string }>;
|
|
1510
1338
|
};
|
|
1511
1339
|
docEdit: {
|
|
1512
1340
|
run: (payload: { path: string; before: string; instruction: string; documentText?: string }) => Promise<DocEditResult>;
|
|
@@ -1616,8 +1444,6 @@ export interface SessionManagerAPI {
|
|
|
1616
1444
|
| { ok: true; text: string }
|
|
1617
1445
|
| { ok: false; error?: string }
|
|
1618
1446
|
>;
|
|
1619
|
-
/** Write side — copies a Capture-panel screenshot data URL to the OS clipboard. */
|
|
1620
|
-
copyImage: (dataUrl: string) => Promise<{ ok: boolean; error?: string }>;
|
|
1621
1447
|
/** Write side — copies arbitrary text (e.g. a recorded flow export) to the OS clipboard. */
|
|
1622
1448
|
writeText: (text: string) => Promise<{ ok: boolean; error?: string }>;
|
|
1623
1449
|
};
|
|
@@ -1704,27 +1530,6 @@ export interface SessionManagerAPI {
|
|
|
1704
1530
|
* renderer needs per-row badges without a separate git call per file. */
|
|
1705
1531
|
fileStatus: (cwd: string) => Promise<GitFileStatusMap>;
|
|
1706
1532
|
};
|
|
1707
|
-
webRemote: {
|
|
1708
|
-
getStatus: () => Promise<WebRemoteStatus>;
|
|
1709
|
-
enable: () => Promise<WebRemoteMutationResult>;
|
|
1710
|
-
disable: () => Promise<WebRemoteMutationResult>;
|
|
1711
|
-
/** Allow MUTATE-tier commands (pty spawn/write, scheduler writes). Default off. */
|
|
1712
|
-
enableControl: () => Promise<WebRemoteMutationResult>;
|
|
1713
|
-
/** Block MUTATE-tier commands — mobile becomes read-only mirror. */
|
|
1714
|
-
disableControl: () => Promise<WebRemoteMutationResult>;
|
|
1715
|
-
pair: (otp: string) => Promise<WebRemotePairResult>;
|
|
1716
|
-
revokeDevice: (deviceId: string) => Promise<WebRemoteMutationResult>;
|
|
1717
|
-
auditTail: (lines?: number) => Promise<WebRemoteAuditTailResult>;
|
|
1718
|
-
onStatus: (handler: (status: WebRemoteStatus) => void) => () => void;
|
|
1719
|
-
onTokenRevoked: (handler: (ev: { deviceId: string }) => void) => () => void;
|
|
1720
|
-
/** Revoke ALL paired devices and tear down every session immediately (panic). */
|
|
1721
|
-
revokeAll: () => Promise<WebRemoteMutationResult>;
|
|
1722
|
-
/** Subscribe to the completion event broadcast after revokeAll. */
|
|
1723
|
-
onRevokedAll: (handler: (ev: { revokedCount: number }) => void) => () => void;
|
|
1724
|
-
/** Confirm that the SAS shown on the desktop matches the browser — marks the
|
|
1725
|
-
* E2E session as authenticated and unblocks MUTATE-tier commands. */
|
|
1726
|
-
confirmSas: () => Promise<WebRemoteMutationResult>;
|
|
1727
|
-
};
|
|
1728
1533
|
chat: {
|
|
1729
1534
|
/** Spawn a headless `claude -p` run for a tab. Results arrive via the on* listeners. */
|
|
1730
1535
|
run: (payload: ChatRunPayload) => Promise<{ ok: boolean }>;
|
package/src/preload/index.cjs
CHANGED
|
@@ -56,66 +56,6 @@ contextBridge.exposeInMainWorld('api', {
|
|
|
56
56
|
return () => ipcRenderer.removeListener('pty:write-error', listener);
|
|
57
57
|
},
|
|
58
58
|
},
|
|
59
|
-
browser: {
|
|
60
|
-
create: (payload) => ipcRenderer.invoke('browser:create', payload),
|
|
61
|
-
setBounds: (payload) => ipcRenderer.invoke('browser:set-bounds', payload),
|
|
62
|
-
show: (viewId) => ipcRenderer.invoke('browser:show', { viewId }),
|
|
63
|
-
hide: (viewId) => ipcRenderer.invoke('browser:hide', { viewId }),
|
|
64
|
-
destroy: (viewId) => ipcRenderer.invoke('browser:destroy', { viewId }),
|
|
65
|
-
navigate: (payload) => ipcRenderer.invoke('browser:navigate', payload),
|
|
66
|
-
back: (viewId) => ipcRenderer.invoke('browser:back', { viewId }),
|
|
67
|
-
forward: (viewId) => ipcRenderer.invoke('browser:forward', { viewId }),
|
|
68
|
-
reload: (viewId) => ipcRenderer.invoke('browser:reload', { viewId }),
|
|
69
|
-
stop: (viewId) => ipcRenderer.invoke('browser:stop', { viewId }),
|
|
70
|
-
onNavState: (viewId, handler) => {
|
|
71
|
-
const channel = `browser:nav-state:${viewId}`;
|
|
72
|
-
const listener = (_e, state) => handler(state);
|
|
73
|
-
ipcRenderer.on(channel, listener);
|
|
74
|
-
return () => ipcRenderer.removeListener(channel, listener);
|
|
75
|
-
},
|
|
76
|
-
recordStart: (viewId) => ipcRenderer.invoke('browser:record-start', { viewId }),
|
|
77
|
-
recordStop: (viewId) => ipcRenderer.invoke('browser:record-stop', { viewId }),
|
|
78
|
-
onRecordStep: (viewId, handler) => {
|
|
79
|
-
const channel = `browser:record-step:${viewId}`;
|
|
80
|
-
const listener = (_e, step) => handler(step);
|
|
81
|
-
ipcRenderer.on(channel, listener);
|
|
82
|
-
return () => ipcRenderer.removeListener(channel, listener);
|
|
83
|
-
},
|
|
84
|
-
captureDom: (payload) => ipcRenderer.invoke('browser:capture-dom', payload),
|
|
85
|
-
captureShot: (viewId) => ipcRenderer.invoke('browser:capture-shot', { viewId }),
|
|
86
|
-
saveBinary: (path, base64, writer) => ipcRenderer.invoke('browser:save-binary', { path, base64, writer }),
|
|
87
|
-
saveRecording: (payload) => ipcRenderer.invoke('browser:save-recording', payload),
|
|
88
|
-
replay: (payload) => ipcRenderer.invoke('browser:replay', payload),
|
|
89
|
-
onReplayStep: (viewId, handler) => {
|
|
90
|
-
const channel = `browser:replay-step:${viewId}`;
|
|
91
|
-
const listener = (_e, step) => handler(step);
|
|
92
|
-
ipcRenderer.on(channel, listener);
|
|
93
|
-
return () => ipcRenderer.removeListener(channel, listener);
|
|
94
|
-
},
|
|
95
|
-
setZoom: (payload) => ipcRenderer.invoke('browser:set-zoom', payload),
|
|
96
|
-
find: (payload) => ipcRenderer.invoke('browser:find', payload),
|
|
97
|
-
stopFind: (viewId) => ipcRenderer.invoke('browser:stop-find', { viewId }),
|
|
98
|
-
onFindResult: (viewId, handler) => {
|
|
99
|
-
const channel = `browser:find-result:${viewId}`;
|
|
100
|
-
const listener = (_e, result) => handler(result);
|
|
101
|
-
ipcRenderer.on(channel, listener);
|
|
102
|
-
return () => ipcRenderer.removeListener(channel, listener);
|
|
103
|
-
},
|
|
104
|
-
pickerStart: (viewId) => ipcRenderer.invoke('browser:picker-start', { viewId }),
|
|
105
|
-
pickerStop: (viewId) => ipcRenderer.invoke('browser:picker-stop', { viewId }),
|
|
106
|
-
onPickerEvent: (viewId, handler) => {
|
|
107
|
-
const channel = `browser:picker-event:${viewId}`;
|
|
108
|
-
const listener = (_e, ev) => handler(ev);
|
|
109
|
-
ipcRenderer.on(channel, listener);
|
|
110
|
-
return () => ipcRenderer.removeListener(channel, listener);
|
|
111
|
-
},
|
|
112
|
-
capture: (payload) => ipcRenderer.invoke('browser:capture', payload),
|
|
113
|
-
onOpenTabRequest: (handler) => {
|
|
114
|
-
const listener = (_e, payload) => handler(payload);
|
|
115
|
-
ipcRenderer.on('browser:open-tab-request', listener);
|
|
116
|
-
return () => ipcRenderer.removeListener('browser:open-tab-request', listener);
|
|
117
|
-
},
|
|
118
|
-
},
|
|
119
59
|
transcripts: {
|
|
120
60
|
subscribe: (payload) => ipcRenderer.invoke('transcript:subscribe', payload),
|
|
121
61
|
unsubscribe: (tabId) => ipcRenderer.invoke('transcript:unsubscribe', { tabId }),
|
|
@@ -246,6 +186,7 @@ contextBridge.exposeInMainWorld('api', {
|
|
|
246
186
|
rename: (path, newName) => ipcRenderer.invoke('files:rename', { path, newName }),
|
|
247
187
|
delete: (path) => ipcRenderer.invoke('files:delete', { path }),
|
|
248
188
|
duplicate: (path) => ipcRenderer.invoke('files:duplicate', { path }),
|
|
189
|
+
saveBinary: (path, base64, writer) => ipcRenderer.invoke('files:save-binary', { path, base64, writer }),
|
|
249
190
|
},
|
|
250
191
|
docEdit: {
|
|
251
192
|
run: (payload) => ipcRenderer.invoke('docedit:run', payload),
|
|
@@ -309,7 +250,6 @@ contextBridge.exposeInMainWorld('api', {
|
|
|
309
250
|
clipboard: {
|
|
310
251
|
pasteImage: () => ipcRenderer.invoke('clipboard:paste-image'),
|
|
311
252
|
pasteText: () => ipcRenderer.invoke('clipboard:paste-text'),
|
|
312
|
-
copyImage: (dataUrl) => ipcRenderer.invoke('browser:copy-image', { dataUrl }),
|
|
313
253
|
writeText: (text) => ipcRenderer.invoke('clipboard:write-text', { text }),
|
|
314
254
|
},
|
|
315
255
|
memory: {
|
|
@@ -372,45 +312,6 @@ contextBridge.exposeInMainWorld('api', {
|
|
|
372
312
|
status: (cwd) => ipcRenderer.invoke('git:status', { cwd }),
|
|
373
313
|
fileStatus: (cwd) => ipcRenderer.invoke('git:file-status', { cwd }),
|
|
374
314
|
},
|
|
375
|
-
webRemote: {
|
|
376
|
-
/** Current connection state (no token values). */
|
|
377
|
-
getStatus: () => ipcRenderer.invoke('webRemote:get-status'),
|
|
378
|
-
/** Turn remote control on. Initiates relay connection if a device is paired. */
|
|
379
|
-
enable: () => ipcRenderer.invoke('webRemote:enable'),
|
|
380
|
-
/** Turn remote control off. Immediately drops relay connection. */
|
|
381
|
-
disable: () => ipcRenderer.invoke('webRemote:disable'),
|
|
382
|
-
/** Allow MUTATE-tier commands (pty spawn/write, scheduler writes). Default off. */
|
|
383
|
-
enableControl: () => ipcRenderer.invoke('webRemote:enable-control'),
|
|
384
|
-
/** Block MUTATE-tier commands — mobile becomes read-only mirror. */
|
|
385
|
-
disableControl: () => ipcRenderer.invoke('webRemote:disable-control'),
|
|
386
|
-
/** Pair a new device using the 8-character OTP shown in the web UI. */
|
|
387
|
-
pair: (otp) => ipcRenderer.invoke('webRemote:pair', { otp }),
|
|
388
|
-
/** Revoke a paired device by its deviceId. */
|
|
389
|
-
revokeDevice: (deviceId) => ipcRenderer.invoke('webRemote:revoke-device', { deviceId }),
|
|
390
|
-
/** Read the last N lines of today's audit log (default 50). */
|
|
391
|
-
auditTail: (lines) => ipcRenderer.invoke('webRemote:audit-tail', lines ? { lines } : {}),
|
|
392
|
-
/** Push event from main when connection status changes. */
|
|
393
|
-
onStatus: (handler) => {
|
|
394
|
-
const listener = (_e, payload) => handler(payload);
|
|
395
|
-
ipcRenderer.on('webRemote:status', listener);
|
|
396
|
-
return () => ipcRenderer.removeListener('webRemote:status', listener);
|
|
397
|
-
},
|
|
398
|
-
/** Push event when the relay revokes this device's token. */
|
|
399
|
-
onTokenRevoked: (handler) => {
|
|
400
|
-
const listener = (_e, payload) => handler(payload);
|
|
401
|
-
ipcRenderer.on('webRemote:token-revoked', listener);
|
|
402
|
-
return () => ipcRenderer.removeListener('webRemote:token-revoked', listener);
|
|
403
|
-
},
|
|
404
|
-
/** Revoke ALL paired devices and tear down every session immediately. */
|
|
405
|
-
revokeAll: () => ipcRenderer.invoke('webRemote:revoke-all'),
|
|
406
|
-
confirmSas: () => ipcRenderer.invoke('webRemote:confirm-sas'),
|
|
407
|
-
/** Push event when revokeAll completes (main broadcasts webRemote:revoked-all). */
|
|
408
|
-
onRevokedAll: (handler) => {
|
|
409
|
-
const listener = (_e, payload) => handler(payload);
|
|
410
|
-
ipcRenderer.on('webRemote:revoked-all', listener);
|
|
411
|
-
return () => ipcRenderer.removeListener('webRemote:revoked-all', listener);
|
|
412
|
-
},
|
|
413
|
-
},
|
|
414
315
|
chat: {
|
|
415
316
|
/** Spawn a headless claude -p job. Results arrive via the on* listeners. */
|
|
416
317
|
run: (payload) => ipcRenderer.invoke('chat:run', payload),
|