@ericsanchezok/synergy-workbench 0.0.0-stage → 3.1.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/AGENTS.md +30 -0
- package/README.md +16 -2
- package/dist/agents.js +11 -0
- package/dist/cli-adapter.js +8 -0
- package/dist/component.js +65 -0
- package/dist/config-schema.js +265 -0
- package/dist/execution/activity.js +78 -0
- package/dist/execution/content.js +184 -0
- package/dist/execution/json-sections.js +231 -0
- package/dist/execution/presentation.js +79 -0
- package/dist/execution/preview.js +13 -0
- package/dist/execution/process.js +125 -0
- package/dist/execution/projection.js +327 -0
- package/dist/execution/query.js +18 -0
- package/dist/execution/routes.js +189 -0
- package/dist/execution/schema.js +322 -0
- package/dist/execution/service.js +1993 -0
- package/dist/features.js +22 -0
- package/dist/http.js +65 -0
- package/dist/performance/analysis.js +330 -0
- package/dist/performance/catalog.js +391 -0
- package/dist/performance/dashboard.js +483 -0
- package/dist/performance/error.js +15 -0
- package/dist/performance/index.js +6 -0
- package/dist/performance/inflight.js +43 -0
- package/dist/performance/projection.js +138 -0
- package/dist/performance/prompt/base.txt +38 -0
- package/dist/performance/prompt/builder.js +21 -0
- package/dist/performance/routes/performance-route.js +439 -0
- package/dist/performance/schema.js +628 -0
- package/dist/performance/timeline.js +98 -0
- package/dist/performance/trace-detail.js +91 -0
- package/dist/presentation/service.js +270 -0
- package/dist/presentation/tools/workbench-view.js +65 -0
- package/dist/presentation/tools.js +10 -0
- package/dist/project/directories.js +363 -0
- package/dist/project/git-health.js +498 -0
- package/dist/project/ide.js +66 -0
- package/dist/project/migration.js +22 -0
- package/dist/project/routes/directories.js +89 -0
- package/dist/project/routes/git.js +30 -0
- package/dist/project/routes/task-defaults.js +42 -0
- package/dist/project/session-health.js +9 -0
- package/dist/project/startup.js +28 -0
- package/dist/project/task-defaults.js +68 -0
- package/dist/project/tools/session-control.js +664 -0
- package/dist/project/tools/session-control.txt +60 -0
- package/dist/project/tools/worktree-archive.js +76 -0
- package/dist/project/tools/worktree-enter.js +175 -0
- package/dist/project/tools/worktree-leave.js +119 -0
- package/dist/project/tools/worktree-list.js +52 -0
- package/dist/project/tools.js +26 -0
- package/dist/project/vcs-branch-watcher.js +76 -0
- package/dist/project/vcs.js +114 -0
- package/dist/project/worktree-janitor.js +140 -0
- package/dist/project/worktrees.js +98 -0
- package/dist/push/bridge.js +171 -0
- package/dist/push/index.js +4 -0
- package/dist/push/routes/push.js +224 -0
- package/dist/push/service.js +93 -0
- package/dist/push/store.js +121 -0
- package/dist/push/types.js +58 -0
- package/dist/review/git.js +226 -0
- package/dist/review/routes.js +45 -0
- package/dist/review/schema.js +32 -0
- package/dist/review/state.js +79 -0
- package/dist/stats/aggregator.js +252 -0
- package/dist/stats/canonical.js +89 -0
- package/dist/stats/cli/stats.js +302 -0
- package/dist/stats/engine.js +176 -0
- package/dist/stats/index.js +5 -0
- package/dist/stats/rollup.js +460 -0
- package/dist/stats/routes/stats.js +94 -0
- package/dist/stats/routes/usage.js +83 -0
- package/dist/stats/storage.js +86 -0
- package/dist/stats/types.js +267 -0
- package/dist/worker.js +6 -0
- package/package.json +232 -4
- package/src/agents.ts +15 -0
- package/src/cli-adapter.ts +11 -0
- package/src/component.ts +73 -0
- package/src/config-schema.ts +289 -0
- package/src/execution/activity.ts +83 -0
- package/src/execution/content.ts +189 -0
- package/src/execution/json-sections.ts +230 -0
- package/src/execution/presentation.ts +91 -0
- package/src/execution/preview.ts +12 -0
- package/src/execution/process.ts +118 -0
- package/src/execution/projection.ts +330 -0
- package/src/execution/query.ts +19 -0
- package/src/execution/routes.ts +324 -0
- package/src/execution/schema.ts +336 -0
- package/src/execution/service.ts +2149 -0
- package/src/features.ts +22 -0
- package/src/http.ts +75 -0
- package/src/performance/analysis.ts +356 -0
- package/src/performance/catalog.ts +902 -0
- package/src/performance/dashboard.ts +521 -0
- package/src/performance/error.ts +32 -0
- package/src/performance/index.ts +6 -0
- package/src/performance/inflight.ts +46 -0
- package/src/performance/projection.ts +143 -0
- package/src/performance/prompt/base.txt +38 -0
- package/src/performance/prompt/builder.ts +24 -0
- package/src/performance/routes/performance-route.ts +541 -0
- package/src/performance/schema.ts +695 -0
- package/src/performance/timeline.ts +100 -0
- package/src/performance/trace-detail.ts +97 -0
- package/src/presentation/service.ts +303 -0
- package/src/presentation/tools/workbench-view.ts +75 -0
- package/src/presentation/tools.ts +11 -0
- package/src/project/directories.ts +391 -0
- package/src/project/git-health.ts +577 -0
- package/src/project/ide.ts +80 -0
- package/src/project/migration.ts +24 -0
- package/src/project/routes/directories.ts +124 -0
- package/src/project/routes/git.ts +42 -0
- package/src/project/routes/task-defaults.ts +50 -0
- package/src/project/session-health.ts +10 -0
- package/src/project/startup.ts +28 -0
- package/src/project/task-defaults.ts +70 -0
- package/src/project/tools/session-control.ts +725 -0
- package/src/project/tools/session-control.txt +60 -0
- package/src/project/tools/worktree-archive.ts +92 -0
- package/src/project/tools/worktree-enter.ts +227 -0
- package/src/project/tools/worktree-leave.ts +140 -0
- package/src/project/tools/worktree-list.ts +68 -0
- package/src/project/tools.ts +29 -0
- package/src/project/vcs-branch-watcher.ts +81 -0
- package/src/project/vcs.ts +124 -0
- package/src/project/worktree-janitor.ts +153 -0
- package/src/project/worktrees.ts +110 -0
- package/src/push/bridge.ts +182 -0
- package/src/push/index.ts +4 -0
- package/src/push/routes/push.ts +267 -0
- package/src/push/service.ts +108 -0
- package/src/push/store.ts +120 -0
- package/src/push/types.ts +67 -0
- package/src/review/git.ts +221 -0
- package/src/review/routes.ts +65 -0
- package/src/review/schema.ts +34 -0
- package/src/review/state.ts +77 -0
- package/src/stats/aggregator.ts +277 -0
- package/src/stats/canonical.ts +93 -0
- package/src/stats/cli/stats.ts +348 -0
- package/src/stats/engine.ts +197 -0
- package/src/stats/index.ts +5 -0
- package/src/stats/rollup.ts +522 -0
- package/src/stats/routes/stats.ts +107 -0
- package/src/stats/routes/usage.ts +103 -0
- package/src/stats/storage.ts +78 -0
- package/src/stats/types.ts +351 -0
- package/src/worker.ts +7 -0
package/src/component.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { componentFeatures } from "./features"
|
|
2
|
+
import { BusEvent } from "@ericsanchezok/synergy-harness/bus/bus-event"
|
|
3
|
+
import { Vcs } from "./project/vcs"
|
|
4
|
+
import { Ide } from "./project/ide"
|
|
5
|
+
import { ReviewState } from "./review/state"
|
|
6
|
+
import { ExecutionSchema } from "./execution/schema"
|
|
7
|
+
import { registerProjectMigrations } from "./project/migration"
|
|
8
|
+
import { PushBridge } from "./push/bridge"
|
|
9
|
+
import type { RuntimeComponent } from "@ericsanchezok/synergy-harness/lifecycle"
|
|
10
|
+
import { version } from "../package.json" with { type: "json" }
|
|
11
|
+
import { registerWorkbenchAgents } from "./agents"
|
|
12
|
+
import { registerProjectTools } from "./project/tools"
|
|
13
|
+
import { registerProjectStartup } from "./project/startup"
|
|
14
|
+
import { registerProjectSessionHealth } from "./project/session-health"
|
|
15
|
+
import { registerConfig } from "./config-schema"
|
|
16
|
+
import { Usage } from "@ericsanchezok/synergy-harness/usage"
|
|
17
|
+
import { ExecutionService } from "./execution/service"
|
|
18
|
+
import { WorkbenchPresentation } from "./presentation/service"
|
|
19
|
+
import { registerPresentationTools } from "./presentation/tools"
|
|
20
|
+
|
|
21
|
+
export function workbench(): RuntimeComponent {
|
|
22
|
+
return {
|
|
23
|
+
id: "workbench",
|
|
24
|
+
features: componentFeatures,
|
|
25
|
+
apiVersion: 1,
|
|
26
|
+
version,
|
|
27
|
+
requires: { "local-runtime": version },
|
|
28
|
+
workers: { agent: new URL("./worker.ts", import.meta.url) },
|
|
29
|
+
services() {
|
|
30
|
+
let dispose: (() => void) | undefined
|
|
31
|
+
let stopUsage: (() => Promise<void>) | undefined
|
|
32
|
+
let stopExecution: (() => void) | undefined
|
|
33
|
+
let stopPresentation: (() => void) | undefined
|
|
34
|
+
return {
|
|
35
|
+
resident: {
|
|
36
|
+
async start() {
|
|
37
|
+
dispose = PushBridge.init()
|
|
38
|
+
stopUsage = Usage.service()
|
|
39
|
+
stopExecution = ExecutionService.init()
|
|
40
|
+
stopPresentation = WorkbenchPresentation.init()
|
|
41
|
+
},
|
|
42
|
+
async stop() {
|
|
43
|
+
stopPresentation?.()
|
|
44
|
+
stopPresentation = undefined
|
|
45
|
+
await stopUsage?.()
|
|
46
|
+
stopExecution?.()
|
|
47
|
+
stopUsage = undefined
|
|
48
|
+
dispose?.()
|
|
49
|
+
dispose = undefined
|
|
50
|
+
await PushBridge.flush()
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
adapters: { cli: new URL("./cli-adapter.ts", import.meta.url), http: new URL("./http.ts", import.meta.url) },
|
|
56
|
+
register() {
|
|
57
|
+
BusEvent.register(
|
|
58
|
+
...Object.values(Vcs.Event),
|
|
59
|
+
...Object.values(Ide.Event),
|
|
60
|
+
ReviewState.Updated,
|
|
61
|
+
ExecutionSchema.Updated,
|
|
62
|
+
ExecutionSchema.ProjectionUpdated,
|
|
63
|
+
)
|
|
64
|
+
registerConfig()
|
|
65
|
+
registerProjectMigrations()
|
|
66
|
+
registerWorkbenchAgents()
|
|
67
|
+
registerProjectTools()
|
|
68
|
+
registerPresentationTools()
|
|
69
|
+
registerProjectStartup()
|
|
70
|
+
registerProjectSessionHealth()
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { ConfigDomain } from "@ericsanchezok/synergy-harness/config/domain"
|
|
2
|
+
import z from "zod"
|
|
3
|
+
import { ConfigExtensions } from "@ericsanchezok/synergy-harness/config/extensions"
|
|
4
|
+
export const Keybinds = z
|
|
5
|
+
.object({
|
|
6
|
+
leader: z.string().optional().default("ctrl+x").describe("Leader key for keybind combinations"),
|
|
7
|
+
app_exit: z.string().optional().default("ctrl+c,ctrl+d,<leader>q").describe("Exit the application"),
|
|
8
|
+
editor_open: z.string().optional().default("<leader>e").describe("Open external editor"),
|
|
9
|
+
theme_list: z.string().optional().default("<leader>t").describe("List available themes"),
|
|
10
|
+
sidebar_toggle: z.string().optional().default("<leader>b").describe("Toggle sidebar"),
|
|
11
|
+
scrollbar_toggle: z.string().optional().default("none").describe("Toggle session scrollbar"),
|
|
12
|
+
username_toggle: z.string().optional().default("none").describe("Toggle username visibility"),
|
|
13
|
+
status_view: z.string().optional().default("<leader>s").describe("View status"),
|
|
14
|
+
session_export: z.string().optional().default("<leader>x").describe("Export session to editor"),
|
|
15
|
+
session_new: z.string().optional().default("<leader>n").describe("Create a new session"),
|
|
16
|
+
session_list: z.string().optional().default("<leader>l").describe("List all sessions"),
|
|
17
|
+
session_timeline: z.string().optional().default("<leader>g").describe("Show session timeline"),
|
|
18
|
+
session_fork: z.string().optional().default("none").describe("Fork session from message"),
|
|
19
|
+
session_rename: z.string().optional().default("none").describe("Rename session"),
|
|
20
|
+
session_interrupt: z.string().optional().default("escape").describe("Interrupt current session"),
|
|
21
|
+
session_compact: z.string().optional().default("<leader>c").describe("Compact the session"),
|
|
22
|
+
messages_page_up: z.string().optional().default("pageup").describe("Scroll messages up by one page"),
|
|
23
|
+
messages_page_down: z.string().optional().default("pagedown").describe("Scroll messages down by one page"),
|
|
24
|
+
messages_half_page_up: z.string().optional().default("ctrl+alt+u").describe("Scroll messages up by half page"),
|
|
25
|
+
messages_half_page_down: z.string().optional().default("ctrl+alt+d").describe("Scroll messages down by half page"),
|
|
26
|
+
messages_first: z.string().optional().default("ctrl+g,home").describe("Navigate to first message"),
|
|
27
|
+
messages_last: z.string().optional().default("ctrl+alt+g,end").describe("Navigate to last message"),
|
|
28
|
+
messages_next: z.string().optional().default("none").describe("Navigate to next message"),
|
|
29
|
+
messages_previous: z.string().optional().default("none").describe("Navigate to previous message"),
|
|
30
|
+
messages_last_user: z.string().optional().default("none").describe("Navigate to last user message"),
|
|
31
|
+
messages_copy: z.string().optional().default("<leader>y").describe("Copy message"),
|
|
32
|
+
messages_undo: z.string().optional().default("<leader>u").describe("Undo message history only"),
|
|
33
|
+
messages_redo: z.string().optional().default("<leader>r").describe("Redo message history only"),
|
|
34
|
+
messages_toggle_conceal: z
|
|
35
|
+
.string()
|
|
36
|
+
.optional()
|
|
37
|
+
.default("<leader>h")
|
|
38
|
+
.describe("Toggle code block concealment in messages"),
|
|
39
|
+
tool_details: z.string().optional().default("none").describe("Toggle tool details visibility"),
|
|
40
|
+
model_list: z.string().optional().default("<leader>m").describe("List available models"),
|
|
41
|
+
model_cycle_recent: z.string().optional().default("f2").describe("Next recently used model"),
|
|
42
|
+
model_cycle_recent_reverse: z.string().optional().default("shift+f2").describe("Previous recently used model"),
|
|
43
|
+
model_cycle_favorite: z.string().optional().default("none").describe("Next favorite model"),
|
|
44
|
+
model_cycle_favorite_reverse: z.string().optional().default("none").describe("Previous favorite model"),
|
|
45
|
+
command_list: z.string().optional().default("ctrl+p").describe("List available commands"),
|
|
46
|
+
agent_list: z.string().optional().default("<leader>a").describe("List agents"),
|
|
47
|
+
agent_cycle: z.string().optional().default("tab").describe("Next agent"),
|
|
48
|
+
agent_cycle_reverse: z.string().optional().default("shift+tab").describe("Previous agent"),
|
|
49
|
+
variant_cycle: z.string().optional().default("ctrl+t").describe("Cycle model variants"),
|
|
50
|
+
input_clear: z.string().optional().default("ctrl+c").describe("Clear input field"),
|
|
51
|
+
input_paste: z.string().optional().default("ctrl+v").describe("Paste from clipboard"),
|
|
52
|
+
input_submit: z.string().optional().default("return").describe("Submit input"),
|
|
53
|
+
input_newline: z
|
|
54
|
+
.string()
|
|
55
|
+
.optional()
|
|
56
|
+
.default("shift+return,ctrl+return,alt+return,ctrl+j")
|
|
57
|
+
.describe("Insert newline in input"),
|
|
58
|
+
input_move_left: z.string().optional().default("left,ctrl+b").describe("Move cursor left in input"),
|
|
59
|
+
input_move_right: z.string().optional().default("right,ctrl+f").describe("Move cursor right in input"),
|
|
60
|
+
input_move_up: z.string().optional().default("up").describe("Move cursor up in input"),
|
|
61
|
+
input_move_down: z.string().optional().default("down").describe("Move cursor down in input"),
|
|
62
|
+
input_select_left: z.string().optional().default("shift+left").describe("Select left in input"),
|
|
63
|
+
input_select_right: z.string().optional().default("shift+right").describe("Select right in input"),
|
|
64
|
+
input_select_up: z.string().optional().default("shift+up").describe("Select up in input"),
|
|
65
|
+
input_select_down: z.string().optional().default("shift+down").describe("Select down in input"),
|
|
66
|
+
input_line_home: z.string().optional().default("ctrl+a").describe("Move to start of line in input"),
|
|
67
|
+
input_line_end: z.string().optional().default("ctrl+e").describe("Move to end of line in input"),
|
|
68
|
+
input_select_line_home: z.string().optional().default("ctrl+shift+a").describe("Select to start of line in input"),
|
|
69
|
+
input_select_line_end: z.string().optional().default("ctrl+shift+e").describe("Select to end of line in input"),
|
|
70
|
+
input_visual_line_home: z.string().optional().default("alt+a").describe("Move to start of visual line in input"),
|
|
71
|
+
input_visual_line_end: z.string().optional().default("alt+e").describe("Move to end of visual line in input"),
|
|
72
|
+
input_select_visual_line_home: z
|
|
73
|
+
.string()
|
|
74
|
+
.optional()
|
|
75
|
+
.default("alt+shift+a")
|
|
76
|
+
.describe("Select to start of visual line in input"),
|
|
77
|
+
input_select_visual_line_end: z
|
|
78
|
+
.string()
|
|
79
|
+
.optional()
|
|
80
|
+
.default("alt+shift+e")
|
|
81
|
+
.describe("Select to end of visual line in input"),
|
|
82
|
+
input_buffer_home: z.string().optional().default("home").describe("Move to start of buffer in input"),
|
|
83
|
+
input_buffer_end: z.string().optional().default("end").describe("Move to end of buffer in input"),
|
|
84
|
+
input_select_buffer_home: z
|
|
85
|
+
.string()
|
|
86
|
+
.optional()
|
|
87
|
+
.default("shift+home")
|
|
88
|
+
.describe("Select to start of buffer in input"),
|
|
89
|
+
input_select_buffer_end: z.string().optional().default("shift+end").describe("Select to end of buffer in input"),
|
|
90
|
+
input_delete_line: z.string().optional().default("ctrl+shift+d").describe("Delete line in input"),
|
|
91
|
+
input_delete_to_line_end: z.string().optional().default("ctrl+k").describe("Delete to end of line in input"),
|
|
92
|
+
input_delete_to_line_start: z.string().optional().default("ctrl+u").describe("Delete to start of line in input"),
|
|
93
|
+
input_backspace: z.string().optional().default("backspace,shift+backspace").describe("Backspace in input"),
|
|
94
|
+
input_delete: z.string().optional().default("ctrl+d,delete,shift+delete").describe("Delete character in input"),
|
|
95
|
+
input_undo: z.string().optional().default("ctrl+-,super+z").describe("Undo in input"),
|
|
96
|
+
input_redo: z.string().optional().default("ctrl+.,super+shift+z").describe("Redo in input"),
|
|
97
|
+
input_word_forward: z
|
|
98
|
+
.string()
|
|
99
|
+
.optional()
|
|
100
|
+
.default("alt+f,alt+right,ctrl+right")
|
|
101
|
+
.describe("Move word forward in input"),
|
|
102
|
+
input_word_backward: z
|
|
103
|
+
.string()
|
|
104
|
+
.optional()
|
|
105
|
+
.default("alt+b,alt+left,ctrl+left")
|
|
106
|
+
.describe("Move word backward in input"),
|
|
107
|
+
input_select_word_forward: z
|
|
108
|
+
.string()
|
|
109
|
+
.optional()
|
|
110
|
+
.default("alt+shift+f,alt+shift+right")
|
|
111
|
+
.describe("Select word forward in input"),
|
|
112
|
+
input_select_word_backward: z
|
|
113
|
+
.string()
|
|
114
|
+
.optional()
|
|
115
|
+
.default("alt+shift+b,alt+shift+left")
|
|
116
|
+
.describe("Select word backward in input"),
|
|
117
|
+
input_delete_word_forward: z
|
|
118
|
+
.string()
|
|
119
|
+
.optional()
|
|
120
|
+
.default("alt+d,alt+delete,ctrl+delete")
|
|
121
|
+
.describe("Delete word forward in input"),
|
|
122
|
+
input_delete_word_backward: z
|
|
123
|
+
.string()
|
|
124
|
+
.optional()
|
|
125
|
+
.default("ctrl+w,ctrl+backspace,alt+backspace")
|
|
126
|
+
.describe("Delete word backward in input"),
|
|
127
|
+
history_previous: z.string().optional().default("up").describe("Previous history item"),
|
|
128
|
+
history_next: z.string().optional().default("down").describe("Next history item"),
|
|
129
|
+
session_child_cycle: z.string().optional().default("<leader>right").describe("Next child session"),
|
|
130
|
+
session_child_cycle_reverse: z.string().optional().default("<leader>left").describe("Previous child session"),
|
|
131
|
+
session_parent: z.string().optional().default("<leader>up").describe("Go to parent session"),
|
|
132
|
+
terminal_suspend: z.string().optional().default("ctrl+z").describe("Suspend terminal"),
|
|
133
|
+
terminal_title_toggle: z.string().optional().default("none").describe("Toggle terminal title"),
|
|
134
|
+
tips_toggle: z.string().optional().default("<leader>h").describe("Toggle tips on home screen"),
|
|
135
|
+
})
|
|
136
|
+
.strict()
|
|
137
|
+
.meta({
|
|
138
|
+
ref: "KeybindsConfig",
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
export const Layout = z.enum(["auto", "stretch"]).meta({
|
|
142
|
+
ref: "LayoutConfig",
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
export type Layout = z.infer<typeof Layout>
|
|
146
|
+
|
|
147
|
+
const QuickSwitcherModel = z
|
|
148
|
+
.object({
|
|
149
|
+
providerID: z.string().describe("Provider id for the quick switcher model preference"),
|
|
150
|
+
modelID: z.string().describe("Model id for the quick switcher model preference"),
|
|
151
|
+
state: z.enum(["add", "remove"]).describe("Whether to force-add or force-remove the model from the quick switcher"),
|
|
152
|
+
})
|
|
153
|
+
.strict()
|
|
154
|
+
.meta({ ref: "QuickSwitcherModelConfig" })
|
|
155
|
+
|
|
156
|
+
export type QuickSwitcherModel = z.infer<typeof QuickSwitcherModel>
|
|
157
|
+
|
|
158
|
+
export const QuickSwitcher = z
|
|
159
|
+
.object({
|
|
160
|
+
models: z.array(QuickSwitcherModel).optional().describe("Per-model quick switcher visibility preferences"),
|
|
161
|
+
})
|
|
162
|
+
.strict()
|
|
163
|
+
.meta({ ref: "QuickSwitcherConfig" })
|
|
164
|
+
|
|
165
|
+
export type QuickSwitcher = z.infer<typeof QuickSwitcher>
|
|
166
|
+
|
|
167
|
+
export const ConfigShape = {
|
|
168
|
+
locale: z.enum(["system", "en", "zh-CN"]).optional().describe("UI locale (system = follow OS, default: system)"),
|
|
169
|
+
theme: z.string().optional().describe("Theme name to use for the interface"),
|
|
170
|
+
activityDisplay: z
|
|
171
|
+
.enum(["full", "balanced", "minimal"])
|
|
172
|
+
.optional()
|
|
173
|
+
.describe(
|
|
174
|
+
"Execution process detail: full = expanded process, balanced = current stage with completed process collapsed, minimal = compact progress. All modes use the same process view (default: balanced)",
|
|
175
|
+
),
|
|
176
|
+
defaultSessionWorkspace: z
|
|
177
|
+
.enum(["main", "worktree"])
|
|
178
|
+
.optional()
|
|
179
|
+
.describe(
|
|
180
|
+
"Default workspace for new sessions started from the Web composer: main = run in the main checkout, " +
|
|
181
|
+
"worktree = start each new session in an isolated git worktree (default: main). " +
|
|
182
|
+
"Programmatic session creation (API, channels, Cortex) always uses the main checkout.",
|
|
183
|
+
),
|
|
184
|
+
defaultSessionEnvironmentProfile: z
|
|
185
|
+
.string()
|
|
186
|
+
.min(1)
|
|
187
|
+
.nullable()
|
|
188
|
+
.optional()
|
|
189
|
+
.describe(
|
|
190
|
+
"Execution profile for new Web/Desktop composer sessions. Omitted follows the global resource default; null disables execution selection. References an existing global profile without defining hosts or credentials.",
|
|
191
|
+
),
|
|
192
|
+
keybinds: Keybinds.optional().describe("Custom keybind configurations"),
|
|
193
|
+
compactReasoning: z
|
|
194
|
+
.boolean()
|
|
195
|
+
.optional()
|
|
196
|
+
.describe("Show a compact reasoning preview in the execution process (default: false)"),
|
|
197
|
+
preferWorkBrief: z
|
|
198
|
+
.boolean()
|
|
199
|
+
.optional()
|
|
200
|
+
.describe("Prefer a tool call's purpose description over its action and target when available (default: false)"),
|
|
201
|
+
quick_switcher: QuickSwitcher.optional().describe("Quick switcher model visibility preferences"),
|
|
202
|
+
welcomeGames: z
|
|
203
|
+
.boolean()
|
|
204
|
+
.optional()
|
|
205
|
+
.describe("Show interactive games on the new-task greeting (default: true). Set false to render a plain greeting."),
|
|
206
|
+
layout: Layout.optional().describe("@deprecated Always uses stretch layout."),
|
|
207
|
+
toast: z
|
|
208
|
+
.object({
|
|
209
|
+
muted: z
|
|
210
|
+
.array(z.enum(["info", "success", "warning", "error"]))
|
|
211
|
+
.optional()
|
|
212
|
+
.describe("Toast types to suppress. The underlying logic still runs but the visual card is not rendered."),
|
|
213
|
+
durationOverrides: z
|
|
214
|
+
.record(z.enum(["info", "success", "warning", "error"]), z.number().int().positive().max(30000))
|
|
215
|
+
.optional()
|
|
216
|
+
.describe("Override auto-dismiss duration in ms per toast type (max 30s)."),
|
|
217
|
+
})
|
|
218
|
+
.strict()
|
|
219
|
+
.optional()
|
|
220
|
+
.describe("Toast notification preferences"),
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export type ConfigValues = z.output<z.ZodObject<typeof ConfigShape>>
|
|
224
|
+
|
|
225
|
+
declare module "@ericsanchezok/synergy-harness/config/schema" {
|
|
226
|
+
interface ConfigExtensionShape extends ConfigShapeType {}
|
|
227
|
+
}
|
|
228
|
+
type ConfigShapeType = typeof ConfigShape
|
|
229
|
+
|
|
230
|
+
const contribution: ConfigExtensions.Contribution = {
|
|
231
|
+
shape: ConfigShape,
|
|
232
|
+
references(raw, providerID) {
|
|
233
|
+
const config = raw as ConfigValues
|
|
234
|
+
return (config.quick_switcher?.models ?? []).flatMap((model, index) =>
|
|
235
|
+
model.providerID === providerID ? [`quick_switcher.models[${index}]`] : [],
|
|
236
|
+
)
|
|
237
|
+
},
|
|
238
|
+
normalize(raw) {
|
|
239
|
+
const result = raw as ConfigValues
|
|
240
|
+
if (result.activityDisplay === undefined) result.activityDisplay = "balanced"
|
|
241
|
+
if (result.compactReasoning === undefined) result.compactReasoning = false
|
|
242
|
+
if (result.preferWorkBrief === undefined) result.preferWorkBrief = false
|
|
243
|
+
if (!result.keybinds) result.keybinds = Keybinds.parse({})
|
|
244
|
+
},
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function registerConfig() {
|
|
248
|
+
ConfigExtensions.register("workbench", contribution)
|
|
249
|
+
for (const domain of [
|
|
250
|
+
{
|
|
251
|
+
id: "general",
|
|
252
|
+
filename: "00-general.jsonc",
|
|
253
|
+
label: "General",
|
|
254
|
+
ownedKeys: [
|
|
255
|
+
"theme",
|
|
256
|
+
"keybinds",
|
|
257
|
+
"toast",
|
|
258
|
+
"compactReasoning",
|
|
259
|
+
"preferWorkBrief",
|
|
260
|
+
"welcomeGames",
|
|
261
|
+
"locale",
|
|
262
|
+
"activityDisplay",
|
|
263
|
+
"defaultSessionWorkspace",
|
|
264
|
+
"defaultSessionEnvironmentProfile",
|
|
265
|
+
"layout",
|
|
266
|
+
],
|
|
267
|
+
mergePolicy: "merge",
|
|
268
|
+
reloadTargets: ["config"],
|
|
269
|
+
uiSection: "general",
|
|
270
|
+
importable: true,
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
id: "models",
|
|
274
|
+
filename: "10-models.jsonc",
|
|
275
|
+
label: "Models",
|
|
276
|
+
ownedKeys: ["quick_switcher"],
|
|
277
|
+
mergePolicy: "merge",
|
|
278
|
+
reloadTargets: ["config"],
|
|
279
|
+
uiSection: "models",
|
|
280
|
+
importable: true,
|
|
281
|
+
},
|
|
282
|
+
] satisfies ConfigDomain.Definition[])
|
|
283
|
+
ConfigDomain.register(domain)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export async function readConfig(): Promise<ConfigValues> {
|
|
287
|
+
const { Config } = await import("@ericsanchezok/synergy-harness/config/config")
|
|
288
|
+
return Config.current()
|
|
289
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { ExecutionSchema } from "./schema"
|
|
2
|
+
|
|
3
|
+
export namespace ExecutionActivity {
|
|
4
|
+
export type Segment = { runID: string | null; from: number; to: number; count: number; rounds: number }
|
|
5
|
+
export function project(rows: readonly ExecutionSchema.Node[], root: string) {
|
|
6
|
+
const byID = new Map(rows.map((node) => [node.id, node]))
|
|
7
|
+
const requests = new Set(rows.filter((node) => node.evidenceKind === "attempt").map((node) => node.parentID))
|
|
8
|
+
const instructions = new Set(
|
|
9
|
+
rows.filter((node) => node.kind === "subtask").map((node) => node.sessionID + ":" + node.runID),
|
|
10
|
+
)
|
|
11
|
+
const nodes = rows
|
|
12
|
+
.filter(
|
|
13
|
+
(node) =>
|
|
14
|
+
node.kind === "subtask" ||
|
|
15
|
+
node.kind === "tool" ||
|
|
16
|
+
(node.kind === "input" &&
|
|
17
|
+
(node.sessionID === root || !instructions.has(node.sessionID + ":" + node.runID))) ||
|
|
18
|
+
node.evidenceKind === "attempt" ||
|
|
19
|
+
(node.evidenceKind === "call" && !requests.has(node.id)),
|
|
20
|
+
)
|
|
21
|
+
.map((node, index) => ({
|
|
22
|
+
...node,
|
|
23
|
+
activity: {
|
|
24
|
+
index,
|
|
25
|
+
count: 1,
|
|
26
|
+
endIndex: index,
|
|
27
|
+
instruction: node.kind === "subtask" || (node.kind === "input" && node.sessionID !== root),
|
|
28
|
+
},
|
|
29
|
+
}))
|
|
30
|
+
const segments: Segment[] = []
|
|
31
|
+
for (const node of nodes) {
|
|
32
|
+
let ancestor: ExecutionSchema.Node | undefined = node
|
|
33
|
+
const seen = new Set<string>()
|
|
34
|
+
while (ancestor && ancestor.sessionID !== root && ancestor.parentID && !seen.has(ancestor.id)) {
|
|
35
|
+
seen.add(ancestor.id)
|
|
36
|
+
ancestor = byID.get(ancestor.parentID)
|
|
37
|
+
}
|
|
38
|
+
const runID = ancestor?.sessionID === root ? ancestor.runID : null
|
|
39
|
+
const last = segments.at(-1)
|
|
40
|
+
if (last?.runID === runID) {
|
|
41
|
+
last.to = node.activity.index
|
|
42
|
+
last.count++
|
|
43
|
+
} else segments.push({ runID, from: node.activity.index, to: node.activity.index, count: 1, rounds: 1 })
|
|
44
|
+
}
|
|
45
|
+
const chunks: Segment[] = []
|
|
46
|
+
const size = Math.max(1, Math.ceil(segments.length / 160))
|
|
47
|
+
for (let index = 0; index < segments.length; index += size) {
|
|
48
|
+
const values = segments.slice(index, index + size)
|
|
49
|
+
chunks.push({
|
|
50
|
+
runID: values.length === 1 ? values[0].runID : null,
|
|
51
|
+
from: values[0].from,
|
|
52
|
+
to: values.at(-1)!.to,
|
|
53
|
+
count: values.reduce((sum, value) => sum + value.count, 0),
|
|
54
|
+
rounds: new Set(values.map((value) => value.runID)).size,
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
nodes,
|
|
59
|
+
segments: chunks,
|
|
60
|
+
humanInputs: nodes.filter((node) => node.kind === "input" && !node.activity.instruction).length,
|
|
61
|
+
taskInstructions: nodes.filter((node) => node.activity.instruction).length,
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export function aggregate(nodes: ReturnType<typeof project>["nodes"], kind: "input" | "model" | "tool") {
|
|
65
|
+
const lane = nodes.filter((node) =>
|
|
66
|
+
kind === "input"
|
|
67
|
+
? node.kind === "input" || node.kind === "subtask"
|
|
68
|
+
: kind === "model"
|
|
69
|
+
? node.evidenceKind === "call" || node.evidenceKind === "attempt"
|
|
70
|
+
: node.kind === "tool",
|
|
71
|
+
)
|
|
72
|
+
const size = Math.max(1, Math.ceil(lane.length / 160))
|
|
73
|
+
const result = []
|
|
74
|
+
for (let index = 0; index < lane.length; index += size) {
|
|
75
|
+
const bucket = lane.slice(index, index + size)
|
|
76
|
+
result.push({
|
|
77
|
+
...bucket[0],
|
|
78
|
+
activity: { ...bucket[0].activity, count: bucket.length, endIndex: bucket.at(-1)!.activity.index },
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
return result
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { ExecutionQueryError, queryCursor } from "./query"
|
|
2
|
+
import { z } from "zod"
|
|
3
|
+
import { RuntimeContext } from "@ericsanchezok/synergy-harness/lifecycle/context"
|
|
4
|
+
import { RolloutEvidence } from "@ericsanchezok/synergy-harness/rollout"
|
|
5
|
+
import { ExecutionJson } from "./json-sections"
|
|
6
|
+
import { ScopeContext } from "@ericsanchezok/synergy-harness/scope/context"
|
|
7
|
+
|
|
8
|
+
export namespace ExecutionContent {
|
|
9
|
+
export type Source = {
|
|
10
|
+
mediaType: string
|
|
11
|
+
bytes: number
|
|
12
|
+
status: "partial" | "complete"
|
|
13
|
+
contentVersion: string
|
|
14
|
+
sha256: string | null
|
|
15
|
+
read: (offset: number, limit: number) => Promise<z.infer<typeof RolloutEvidence.Content>>
|
|
16
|
+
stream: () => AsyncIterable<Uint8Array>
|
|
17
|
+
}
|
|
18
|
+
export const Query = z.object({
|
|
19
|
+
field: z.string().min(1).max(100),
|
|
20
|
+
runID: z.string().optional(),
|
|
21
|
+
version: z.string().max(4096).optional(),
|
|
22
|
+
})
|
|
23
|
+
export const PageQuery = Query.extend({
|
|
24
|
+
cursor: z.string().max(4096).optional(),
|
|
25
|
+
limit: z.coerce.number().int().min(1).max(500).default(100),
|
|
26
|
+
})
|
|
27
|
+
export const SearchQuery = PageQuery.extend({
|
|
28
|
+
query: z.string().min(1).max(500),
|
|
29
|
+
caseSensitive: z.preprocess((value) => (value === "false" ? false : value), z.coerce.boolean()).default(true),
|
|
30
|
+
})
|
|
31
|
+
export const Section = z.object({
|
|
32
|
+
path: z.array(z.string()),
|
|
33
|
+
kind: z.enum(["object", "array", "string", "number", "boolean", "null"]),
|
|
34
|
+
offset: z.number(),
|
|
35
|
+
bytes: z.number(),
|
|
36
|
+
role: z.string().optional(),
|
|
37
|
+
preview: z.string(),
|
|
38
|
+
})
|
|
39
|
+
export const Sections = z
|
|
40
|
+
.object({
|
|
41
|
+
contentVersion: z.string(),
|
|
42
|
+
format: z.enum(["json", "text", "partial"]),
|
|
43
|
+
items: z.array(Section),
|
|
44
|
+
total: z.number(),
|
|
45
|
+
nextCursor: z.string().nullable(),
|
|
46
|
+
truncated: z.boolean(),
|
|
47
|
+
})
|
|
48
|
+
.meta({ ref: "ExecutionContentSections" })
|
|
49
|
+
export const Search = z
|
|
50
|
+
.object({
|
|
51
|
+
contentVersion: z.string(),
|
|
52
|
+
status: z.enum(["partial", "complete"]),
|
|
53
|
+
items: z.array(z.object({ offset: z.number(), bytes: z.number(), preview: z.string() })),
|
|
54
|
+
nextCursor: z.string().nullable(),
|
|
55
|
+
})
|
|
56
|
+
.meta({ ref: "ExecutionContentSearch" })
|
|
57
|
+
const cache = RuntimeContext.state(() => ({
|
|
58
|
+
entries: new Map<string, { value: Awaited<ReturnType<typeof ExecutionJson.index>>; bytes: number }>(),
|
|
59
|
+
bytes: 0,
|
|
60
|
+
}))
|
|
61
|
+
function cursor(version: string, query: string, offset: number) {
|
|
62
|
+
return Buffer.from(JSON.stringify({ version, query, offset })).toString("base64url")
|
|
63
|
+
}
|
|
64
|
+
function position(input: string | undefined, version: string, query: string) {
|
|
65
|
+
if (!input) return 0
|
|
66
|
+
const value = queryCursor(
|
|
67
|
+
z.object({ version: z.string(), query: z.string(), offset: z.number().int().nonnegative() }),
|
|
68
|
+
input,
|
|
69
|
+
)
|
|
70
|
+
if (value.version !== version || value.query !== query)
|
|
71
|
+
throw new ExecutionQueryError("Execution content cursor does not match its version and query")
|
|
72
|
+
return value.offset
|
|
73
|
+
}
|
|
74
|
+
export async function sections(source: Source, input: z.infer<typeof PageQuery>, signal?: AbortSignal) {
|
|
75
|
+
const offset = position(input.cursor, source.contentVersion, "sections")
|
|
76
|
+
if (source.status === "partial" || !source.mediaType.includes("json"))
|
|
77
|
+
return Sections.parse({
|
|
78
|
+
contentVersion: source.contentVersion,
|
|
79
|
+
format: source.status === "partial" ? "partial" : "text",
|
|
80
|
+
items: [],
|
|
81
|
+
total: 0,
|
|
82
|
+
nextCursor: null,
|
|
83
|
+
truncated: false,
|
|
84
|
+
})
|
|
85
|
+
const state = cache()
|
|
86
|
+
const key = ScopeContext.current.scope.id + ":" + source.contentVersion
|
|
87
|
+
let value = state.entries.get(key)?.value
|
|
88
|
+
if (!value) {
|
|
89
|
+
try {
|
|
90
|
+
value = await ExecutionJson.index(source.stream(), signal)
|
|
91
|
+
} catch (error) {
|
|
92
|
+
if (!(error instanceof SyntaxError)) throw error
|
|
93
|
+
return Sections.parse({
|
|
94
|
+
contentVersion: source.contentVersion,
|
|
95
|
+
format: "text",
|
|
96
|
+
items: [],
|
|
97
|
+
total: 0,
|
|
98
|
+
nextCursor: null,
|
|
99
|
+
truncated: false,
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
const bytes = Buffer.byteLength(JSON.stringify(value))
|
|
103
|
+
if (!state.entries.has(key) && bytes <= 8 * 1024 * 1024) {
|
|
104
|
+
while (state.bytes + bytes > 8 * 1024 * 1024 && state.entries.size) {
|
|
105
|
+
const [key, entry] = state.entries.entries().next().value!
|
|
106
|
+
state.entries.delete(key)
|
|
107
|
+
state.bytes -= entry.bytes
|
|
108
|
+
}
|
|
109
|
+
state.entries.set(key, { value, bytes })
|
|
110
|
+
state.bytes += bytes
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const items = value.items.slice(offset, offset + input.limit)
|
|
114
|
+
return Sections.parse({
|
|
115
|
+
contentVersion: source.contentVersion,
|
|
116
|
+
format: "json",
|
|
117
|
+
items,
|
|
118
|
+
total: value.items.length,
|
|
119
|
+
nextCursor:
|
|
120
|
+
offset + items.length < value.items.length
|
|
121
|
+
? cursor(source.contentVersion, "sections", offset + items.length)
|
|
122
|
+
: null,
|
|
123
|
+
truncated: value.truncated,
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
export async function value(source: Source, path: string[], signal?: AbortSignal): Promise<unknown> {
|
|
127
|
+
let next: string | undefined
|
|
128
|
+
let section: z.infer<typeof Section> | undefined
|
|
129
|
+
do {
|
|
130
|
+
const page = await sections(source, PageQuery.parse({ field: "request", cursor: next, limit: 500 }), signal)
|
|
131
|
+
section = page.items.find((item) => JSON.stringify(item.path) === JSON.stringify(path))
|
|
132
|
+
next = page.nextCursor ?? undefined
|
|
133
|
+
} while (!section && next)
|
|
134
|
+
if (!section || section.bytes > 8 * 1024 * 1024) return undefined
|
|
135
|
+
const parts: string[] = []
|
|
136
|
+
for (let offset = section.offset; offset < section.offset + section.bytes;) {
|
|
137
|
+
signal?.throwIfAborted()
|
|
138
|
+
const page = await source.read(offset, Math.min(65_536, section.offset + section.bytes - offset))
|
|
139
|
+
parts.push(page.text)
|
|
140
|
+
offset = page.nextOffset ?? source.bytes
|
|
141
|
+
}
|
|
142
|
+
return JSON.parse(parts.join(""))
|
|
143
|
+
}
|
|
144
|
+
export async function search(source: Source, input: z.infer<typeof SearchQuery>, signal?: AbortSignal) {
|
|
145
|
+
const fingerprint = JSON.stringify([input.query, input.caseSensitive])
|
|
146
|
+
let offset = position(input.cursor, source.contentVersion, fingerprint)
|
|
147
|
+
const pattern = new RegExp(input.query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), input.caseSensitive ? "gu" : "giu")
|
|
148
|
+
const items: Array<{ offset: number; bytes: number; preview: string }> = []
|
|
149
|
+
let carry = ""
|
|
150
|
+
let carryOffset = offset
|
|
151
|
+
let next: number | null = null
|
|
152
|
+
while (offset < source.bytes) {
|
|
153
|
+
signal?.throwIfAborted()
|
|
154
|
+
const page = await source.read(offset, 65_536)
|
|
155
|
+
const text = carry + page.text
|
|
156
|
+
const base = carry ? carryOffset : offset
|
|
157
|
+
const safe = page.nextOffset === null ? text.length : Math.max(0, text.length - input.query.length - 2)
|
|
158
|
+
pattern.lastIndex = 0
|
|
159
|
+
let consumed = 0
|
|
160
|
+
for (let match = pattern.exec(text); match && match.index < safe; match = pattern.exec(text)) {
|
|
161
|
+
const start = base + Buffer.byteLength(text.slice(0, match.index))
|
|
162
|
+
const bytes = Buffer.byteLength(match[0])
|
|
163
|
+
items.push({
|
|
164
|
+
offset: start,
|
|
165
|
+
bytes,
|
|
166
|
+
preview: text.slice(Math.max(0, match.index - 60), match.index + match[0].length + 100),
|
|
167
|
+
})
|
|
168
|
+
consumed = match.index + match[0].length
|
|
169
|
+
if (items.length === input.limit) {
|
|
170
|
+
next = start + bytes
|
|
171
|
+
break
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (next !== null) break
|
|
175
|
+
let retained = Math.max(safe, consumed)
|
|
176
|
+
if (retained && retained < text.length && /[\uDC00-\uDFFF]/.test(text[retained])) retained--
|
|
177
|
+
carry = text.slice(retained)
|
|
178
|
+
carryOffset = base + Buffer.byteLength(text.slice(0, retained))
|
|
179
|
+
offset = page.nextOffset ?? source.bytes
|
|
180
|
+
await new Promise<void>((resolve) => setImmediate(resolve))
|
|
181
|
+
}
|
|
182
|
+
return Search.parse({
|
|
183
|
+
contentVersion: source.contentVersion,
|
|
184
|
+
status: source.status,
|
|
185
|
+
items,
|
|
186
|
+
nextCursor: next !== null && next < source.bytes ? cursor(source.contentVersion, fingerprint, next) : null,
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
}
|