@scrylog/cli 0.3.0 → 0.3.2

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.
Files changed (195) hide show
  1. package/dist/assets/highlights-eq9cgrbb.scm +604 -0
  2. package/dist/assets/highlights-ghv9g403.scm +205 -0
  3. package/dist/assets/highlights-hk7bwhj4.scm +284 -0
  4. package/dist/assets/highlights-r812a2qc.scm +150 -0
  5. package/dist/assets/highlights-x6tmsnaa.scm +115 -0
  6. package/dist/assets/injections-73j83es3.scm +27 -0
  7. package/dist/assets/parser.worker-xyhf83rf.js +4563 -0
  8. package/dist/assets/tree-sitter-3jzf13jk.wasm +0 -0
  9. package/dist/assets/tree-sitter-javascript-nd0q4pe9.wasm +0 -0
  10. package/dist/assets/tree-sitter-markdown-411r6y9b.wasm +0 -0
  11. package/dist/assets/tree-sitter-markdown_inline-j5349f42.wasm +0 -0
  12. package/dist/assets/tree-sitter-typescript-zxjzwt75.wasm +0 -0
  13. package/dist/assets/tree-sitter-zig-e78zbjpm.wasm +0 -0
  14. package/dist/cli/index.js +499 -0
  15. package/dist/cli/index.js.map +61 -0
  16. package/dist/daemon/index.js +670 -0
  17. package/dist/daemon/index.js.map +1475 -0
  18. package/dist/hooks/claude.js +6 -0
  19. package/dist/hooks/claude.js.map +11 -0
  20. package/dist/hooks/codex.js +10 -0
  21. package/dist/hooks/codex.js.map +11 -0
  22. package/dist/mcp/index.js +100 -0
  23. package/dist/mcp/index.js.map +231 -0
  24. package/dist/oc/index.js +21 -0
  25. package/dist/oc/index.js.map +10 -0
  26. package/package.json +16 -13
  27. package/src/api/docs/sessions.keys.ts +0 -6
  28. package/src/api/docs/sessions.ts +0 -82
  29. package/src/api/docs/system.keys.ts +0 -5
  30. package/src/api/docs/system.ts +0 -69
  31. package/src/api/docs/usage.keys.ts +0 -3
  32. package/src/api/docs/usage.ts +0 -24
  33. package/src/api/docs/webhooks.keys.ts +0 -5
  34. package/src/api/docs/webhooks.ts +0 -78
  35. package/src/api/index.ts +0 -18
  36. package/src/api/lib/auth.ts +0 -43
  37. package/src/api/lib/errors.ts +0 -28
  38. package/src/api/lib/route.ts +0 -18
  39. package/src/api/models/claude.ts +0 -4
  40. package/src/api/models/codex.ts +0 -4
  41. package/src/api/models/opencode.ts +0 -11
  42. package/src/api/routes/sessions.ts +0 -51
  43. package/src/api/routes/system.ts +0 -106
  44. package/src/api/routes/usage.ts +0 -15
  45. package/src/api/routes/webhooks/claude.ts +0 -149
  46. package/src/api/routes/webhooks/codex.ts +0 -175
  47. package/src/api/routes/webhooks/opencode.ts +0 -112
  48. package/src/api/routes.ts +0 -93
  49. package/src/api/schemas/common.ts +0 -29
  50. package/src/api/schemas/sessions.ts +0 -86
  51. package/src/api/schemas/usage.ts +0 -51
  52. package/src/api/schemas/webhooks/claude.ts +0 -32
  53. package/src/api/schemas/webhooks/codex.ts +0 -34
  54. package/src/api/schemas/webhooks/opencode.ts +0 -29
  55. package/src/api/services/claude-session.ts +0 -9
  56. package/src/api/services/codex-session.ts +0 -8
  57. package/src/api/services/opencode-session.ts +0 -73
  58. package/src/classifier.ts +0 -206
  59. package/src/cli/commands.ts +0 -714
  60. package/src/cli/index.ts +0 -18
  61. package/src/cli/program.ts +0 -175
  62. package/src/cli/prompt.ts +0 -44
  63. package/src/daemon/cli.ts +0 -27
  64. package/src/daemon/collectors/claude/README.md +0 -108
  65. package/src/daemon/collectors/claude/collector.ts +0 -423
  66. package/src/daemon/collectors/claude/discovery.ts +0 -127
  67. package/src/daemon/collectors/claude/hooks.ts +0 -181
  68. package/src/daemon/collectors/claude/index.ts +0 -8
  69. package/src/daemon/collectors/claude/jsonl.ts +0 -85
  70. package/src/daemon/collectors/claude/transcript.ts +0 -246
  71. package/src/daemon/collectors/codex/README.md +0 -147
  72. package/src/daemon/collectors/codex/collector.ts +0 -661
  73. package/src/daemon/collectors/codex/discovery.ts +0 -104
  74. package/src/daemon/collectors/codex/hooks.ts +0 -179
  75. package/src/daemon/collectors/codex/index.ts +0 -7
  76. package/src/daemon/collectors/codex/jsonl.ts +0 -108
  77. package/src/daemon/collectors/codex/transcript.ts +0 -299
  78. package/src/daemon/collectors/git.ts +0 -353
  79. package/src/daemon/collectors/herdr/collector.ts +0 -128
  80. package/src/daemon/collectors/herdr/index.ts +0 -2
  81. package/src/daemon/collectors/herdr/normalization.ts +0 -111
  82. package/src/daemon/collectors/opencode/README.md +0 -106
  83. package/src/daemon/collectors/opencode/collector.ts +0 -545
  84. package/src/daemon/collectors/opencode/discovery.ts +0 -257
  85. package/src/daemon/collectors/opencode/index.ts +0 -5
  86. package/src/daemon/collectors/opencode/session-builder.ts +0 -59
  87. package/src/daemon/collectors/opencode-db.ts +0 -294
  88. package/src/daemon/collectors/process.ts +0 -201
  89. package/src/daemon/collectors/tmux/README.md +0 -128
  90. package/src/daemon/collectors/tmux/collector.ts +0 -492
  91. package/src/daemon/collectors/tmux/enrichment.ts +0 -242
  92. package/src/daemon/collectors/tmux/index.ts +0 -4
  93. package/src/daemon/collectors/tmux/parse.ts +0 -128
  94. package/src/daemon/collectors/usage.ts +0 -259
  95. package/src/daemon/index.ts +0 -209
  96. package/src/daemon/scheduler.ts +0 -86
  97. package/src/daemon/store.ts +0 -630
  98. package/src/detect.ts +0 -143
  99. package/src/hooks/claude.ts +0 -596
  100. package/src/hooks/codex.ts +0 -517
  101. package/src/legacy/classifier.ts +0 -215
  102. package/src/legacy/detect.ts +0 -143
  103. package/src/mcp/client-server.ts +0 -21
  104. package/src/mcp/http-handler.ts +0 -126
  105. package/src/mcp/index.ts +0 -61
  106. package/src/mcp/resources/config-current.ts +0 -19
  107. package/src/mcp/resources/index.ts +0 -11
  108. package/src/mcp/resources/sessions-active.ts +0 -19
  109. package/src/mcp/resources/sessions-all.ts +0 -19
  110. package/src/mcp/schemas/sessions.ts +0 -3
  111. package/src/mcp/server.ts +0 -17
  112. package/src/mcp/services/api-adapter.ts +0 -48
  113. package/src/mcp/services/data-source.ts +0 -22
  114. package/src/mcp/services/store-adapter.ts +0 -40
  115. package/src/mcp/tools/get-active-sessions.ts +0 -16
  116. package/src/mcp/tools/get-session-children.ts +0 -24
  117. package/src/mcp/tools/get-session-messages.ts +0 -24
  118. package/src/mcp/tools/get-session.ts +0 -23
  119. package/src/mcp/tools/index.ts +0 -15
  120. package/src/mcp/tools/list-sessions.ts +0 -16
  121. package/src/oc/index.ts +0 -302
  122. package/src/service/manager.ts +0 -284
  123. package/src/shared/config.ts +0 -511
  124. package/src/shared/ipc.ts +0 -80
  125. package/src/shared/logger.ts +0 -248
  126. package/src/shared/paths.ts +0 -73
  127. package/src/shared/platform.ts +0 -110
  128. package/src/shared/session-exclusions.ts +0 -5
  129. package/src/shared/session-view.ts +0 -302
  130. package/src/shared/terminal-capabilities.ts +0 -130
  131. package/src/shared/types.ts +0 -330
  132. package/src/shared/utils.ts +0 -339
  133. package/src/shared/version.ts +0 -44
  134. package/src/tui/App.tsx +0 -240
  135. package/src/tui/client.ts +0 -163
  136. package/src/tui/components/BindingsModal.tsx +0 -82
  137. package/src/tui/components/DashboardHeader.tsx +0 -256
  138. package/src/tui/components/KeyBar.tsx +0 -67
  139. package/src/tui/components/SessionDetailModal.tsx +0 -169
  140. package/src/tui/components/SessionsView.tsx +0 -184
  141. package/src/tui/components/TabBar.tsx +0 -40
  142. package/src/tui/components/UsageSummaryPane.tsx +0 -272
  143. package/src/tui/components/UsageView.tsx +0 -242
  144. package/src/tui/components/tool-icons.ts +0 -24
  145. package/src/tui/index.tsx +0 -106
  146. package/src/tui/keybindings.ts +0 -49
  147. package/src/tui/model.ts +0 -295
  148. package/src/tui/state.ts +0 -77
  149. package/src/tui/theme.ts +0 -110
  150. package/src/types.ts +0 -113
  151. package/src/usage/README.md +0 -323
  152. package/src/usage/api.ts +0 -99
  153. package/src/usage/client.ts +0 -3
  154. package/src/usage/clients/anthropic-oauth-usage.ts +0 -258
  155. package/src/usage/clients/anthropic-web-usage.ts +0 -329
  156. package/src/usage/clients/claude-cli-probe.ts +0 -513
  157. package/src/usage/clients/codex-app-server.ts +0 -354
  158. package/src/usage/clients/codex-oauth-usage.ts +0 -240
  159. package/src/usage/clients/opencode-go-api.ts +0 -100
  160. package/src/usage/clients/opencode-go-usage.ts +0 -577
  161. package/src/usage/errors.ts +0 -33
  162. package/src/usage/index.ts +0 -20
  163. package/src/usage/local/opencode-go-local.ts +0 -194
  164. package/src/usage/mappers.ts +0 -282
  165. package/src/usage/planner.ts +0 -118
  166. package/src/usage/providers/claude.ts +0 -233
  167. package/src/usage/providers/codex.ts +0 -115
  168. package/src/usage/providers/opencode-go.ts +0 -115
  169. package/src/usage/resolver.ts +0 -4
  170. package/src/usage/resolvers/browser-cookies.ts +0 -808
  171. package/src/usage/resolvers/claude-auth.ts +0 -208
  172. package/src/usage/resolvers/claude-cli.ts +0 -24
  173. package/src/usage/resolvers/claude-desktop-auth.ts +0 -170
  174. package/src/usage/resolvers/claude-web.ts +0 -82
  175. package/src/usage/resolvers/codex-auth.ts +0 -174
  176. package/src/usage/resolvers/keychain-coordinator.ts +0 -397
  177. package/src/usage/resolvers/opencode-go-web.ts +0 -55
  178. package/src/usage/types.ts +0 -225
  179. package/src/usage/web-session-cache.ts +0 -37
  180. /package/{src/tui → dist/cli}/assets/providers/SOURCES.md +0 -0
  181. /package/{src/tui → dist/cli}/assets/providers/claude-terminal.png +0 -0
  182. /package/{src/tui → dist/cli}/assets/providers/claude-terminal.svg +0 -0
  183. /package/{src/tui → dist/cli}/assets/providers/claude.png +0 -0
  184. /package/{src/tui → dist/cli}/assets/providers/copilot.png +0 -0
  185. /package/{src/tui → dist/cli}/assets/providers/copilot.svg +0 -0
  186. /package/{src/tui → dist/cli}/assets/providers/openai-terminal.png +0 -0
  187. /package/{src/tui → dist/cli}/assets/providers/openai-terminal.svg +0 -0
  188. /package/{src/tui → dist/cli}/assets/providers/openai.png +0 -0
  189. /package/{src/tui → dist/cli}/assets/providers/opencode-terminal.png +0 -0
  190. /package/{src/tui → dist/cli}/assets/providers/opencode-terminal.svg +0 -0
  191. /package/{src/tui → dist/cli}/assets/providers/opencode.png +0 -0
  192. /package/{src/tui → dist/cli}/assets/providers/shell.png +0 -0
  193. /package/{src/tui → dist/cli}/assets/providers/shell.svg +0 -0
  194. /package/{src/tui → dist/cli}/assets/providers/unknown.png +0 -0
  195. /package/{src/tui → dist/cli}/assets/providers/unknown.svg +0 -0
@@ -1,86 +0,0 @@
1
- import { z } from "@hono/zod-openapi";
2
-
3
- const SessionStateSchema = z.enum(["working", "idle", "waiting-input", "error", "shell", "unknown"]);
4
- const SessionToolSchema = z.enum(["opencode", "claude", "codex", "copilotcli", "shell", "unknown"]);
5
-
6
- const ProjectInfoSchema = z.object({
7
- displayName: z.string(),
8
- compactLabel: z.string(),
9
- branch: z.string().optional(),
10
- isGitRepository: z.boolean().optional(),
11
- sourcePath: z.string().optional(),
12
- gitRootPath: z.string().optional(),
13
- repositoryIdentityPath: z.string().optional(),
14
- });
15
-
16
- const TmuxInfoSchema = z.object({
17
- session: z.string(),
18
- window: z.string(),
19
- pane: z.string(),
20
- windowName: z.string().optional(),
21
- });
22
-
23
- const HerdrInfoSchema = z.object({
24
- paneId: z.string(),
25
- terminalId: z.string().optional(),
26
- workspaceId: z.string().optional(),
27
- workspace: z.string().optional(),
28
- tabId: z.string().optional(),
29
- tab: z.string().optional(),
30
- name: z.string().optional(),
31
- agent: z.string().optional(),
32
- });
33
-
34
- export const SessionListItemSchema = z.object({
35
- id: z.string(),
36
- source: z.enum(["tmux", "herdr", "standalone"]),
37
- tool: SessionToolSchema,
38
- state: SessionStateSchema,
39
- summary: z.string(),
40
- lastActivity: z.number(),
41
- stableForMs: z.number().optional(),
42
- hasAttention: z.boolean().optional(),
43
- detectionSource: z.string().optional(),
44
- parentSessionId: z.string().optional(),
45
- displayName: z.string().optional(),
46
- project: ProjectInfoSchema.optional(),
47
- tmux: TmuxInfoSchema.optional(),
48
- herdr: HerdrInfoSchema.optional(),
49
- currentPath: z.string().optional(),
50
- activeTool: z.string().optional(),
51
- childCount: z.number().optional(),
52
- activeChildCount: z.number().optional(),
53
- });
54
-
55
- export const SessionTreeItemSchema = SessionListItemSchema.extend({
56
- children: z.array(SessionListItemSchema),
57
- });
58
-
59
- export const SessionViewCountsSchema = z.object({
60
- total: z.number(),
61
- visible: z.number(),
62
- rootsVisible: z.number(),
63
- childrenVisible: z.number(),
64
- byState: z.record(z.string(), z.number()),
65
- byTool: z.record(z.string(), z.number()),
66
- });
67
-
68
- export const SessionsResponseSchema = z.object({
69
- sessions: z.array(SessionTreeItemSchema),
70
- counts: SessionViewCountsSchema,
71
- generatedAt: z.number(),
72
- });
73
-
74
- export const SessionDetailResponseSchema = z.object({
75
- session: z.record(z.string(), z.unknown()).openapi({ description: "Full SessionModel" }),
76
- });
77
-
78
- export const SessionChildrenResponseSchema = z.object({
79
- sessionId: z.string(),
80
- children: z.array(SessionListItemSchema),
81
- });
82
-
83
- export const SessionMessagesResponseSchema = z.object({
84
- sessionId: z.string(),
85
- messages: z.array(z.unknown()),
86
- });
@@ -1,51 +0,0 @@
1
- import { z } from "@hono/zod-openapi";
2
-
3
- export const UsageWindowSchema = z.object({
4
- id: z.string(),
5
- title: z.string(),
6
- usedPercent: z.number().min(0).max(100),
7
- used: z.number().optional(),
8
- limit: z.number().optional(),
9
- windowMinutes: z.number().optional(),
10
- resetsAt: z.string().datetime().nullable().optional(),
11
- resetInSec: z.number().optional(),
12
- });
13
-
14
- export const ProviderCostSnapshotSchema = z.object({
15
- used: z.number(),
16
- limit: z.number(),
17
- currency: z.string(),
18
- period: z.string(),
19
- balance: z.number().optional(),
20
- resetsAt: z.string().datetime().nullable().optional(),
21
- updatedAt: z.string().datetime(),
22
- });
23
-
24
- export const UsageProviderEntrySchema = z.object({
25
- provider: z.string(),
26
- accountId: z.string().nullable(),
27
- available: z.boolean(),
28
- status: z.enum(["ok", "error", "unavailable", "pending"]),
29
- metadata: z
30
- .object({
31
- dataSource: z.string().optional(),
32
- accountEmail: z.string().nullable().optional(),
33
- accountOrganization: z.string().nullable().optional(),
34
- subscriptionType: z.string().nullable().optional(),
35
- rateLimitTier: z.string().nullable().optional(),
36
- workspaceId: z.string().nullable().optional(),
37
- workspaceName: z.string().nullable().optional(),
38
- })
39
- .optional(),
40
- errors: z.array(z.string()).optional(),
41
- windows: z.array(UsageWindowSchema).optional(),
42
- cost: ProviderCostSnapshotSchema.nullable().optional(),
43
- updatedAt: z.string().datetime().optional(),
44
- });
45
-
46
- export const UsageStateSchema = z.object({
47
- providers: z.array(UsageProviderEntrySchema),
48
- lastRefreshedAt: z.number(),
49
- });
50
-
51
- export const UsageResponseSchema = UsageStateSchema;
@@ -1,32 +0,0 @@
1
- import { z } from "@hono/zod-openapi";
2
-
3
- // Minimal validation: only 'sessionId' is essential. All other fields are optional
4
- // and extra fields pass through transparently (.passthrough()) so future changes
5
- // to the Claude hook format don't cause unexpected rejections.
6
- export const ClaudeWebhookBodySchema = z
7
- .object({
8
- sessionId: z.string().openapi({ example: "abc-123-def" }),
9
- eventType: z.string().optional().openapi({ example: "PreToolUse" }),
10
- eventTime: z.number().optional(),
11
- hookSeq: z.number().optional(),
12
- state: z.enum(["working", "idle", "waiting-input", "error", "shell", "unknown"]).optional(),
13
- hasAttention: z.boolean().optional(),
14
- attentionKind: z.enum(["permission", "question", "turn", "none"]).optional(),
15
- summary: z.string().optional(),
16
- activeTool: z.string().optional(),
17
- toolName: z.string().optional(),
18
- toolInputSummary: z.string().optional(),
19
- toolResultStatus: z.enum(["started", "completed", "error", "blocked", "denied", "unknown"]).optional(),
20
- permissionMode: z.string().optional(),
21
- startSource: z.string().optional(),
22
- endReason: z.string().optional(),
23
- errorType: z.string().optional(),
24
- agentId: z.string().optional(),
25
- agentType: z.string().optional(),
26
- transcriptPath: z.string().optional(),
27
- currentPath: z.string().optional(),
28
- lastActivity: z.number().optional(),
29
- parentSessionId: z.string().optional(),
30
- detectionSource: z.literal("claude-hook").optional(),
31
- })
32
- .passthrough();
@@ -1,34 +0,0 @@
1
- import { z } from "@hono/zod-openapi";
2
-
3
- // Minimal validation: only 'sessionId' is essential. All other fields are optional
4
- // and extra fields pass through transparently (.passthrough()) so future changes
5
- // to the Codex hook format don't cause unexpected rejections.
6
- export const CodexWebhookBodySchema = z
7
- .object({
8
- sessionId: z.string().openapi({ example: "abc-123-def" }),
9
- eventType: z.string().optional().openapi({ example: "PreToolUse" }),
10
- eventTime: z.number().optional(),
11
- hookSeq: z.number().optional(),
12
- state: z.enum(["working", "idle", "waiting-input", "error", "shell", "unknown"]).optional(),
13
- hasAttention: z.boolean().optional(),
14
- attentionKind: z.enum(["permission", "question", "turn", "none"]).optional(),
15
- summary: z.string().optional(),
16
- activeTool: z.string().optional(),
17
- model: z.string().optional(),
18
- toolName: z.string().optional(),
19
- toolInputSummary: z.string().optional(),
20
- toolResultStatus: z.enum(["started", "completed", "error", "blocked", "denied", "unknown"]).optional(),
21
- permissionMode: z.string().optional(),
22
- startSource: z.string().optional(),
23
- endReason: z.string().optional(),
24
- transcriptPath: z.string().optional(),
25
- currentPath: z.string().optional(),
26
- lastActivity: z.number().optional(),
27
- parentSessionId: z.string().optional(),
28
- forkedFromId: z.string().optional(),
29
- turnId: z.string().optional(),
30
- agentId: z.string().optional(),
31
- agentType: z.string().optional(),
32
- detectionSource: z.literal("codex-hook").optional(),
33
- })
34
- .passthrough();
@@ -1,29 +0,0 @@
1
- import { z } from "@hono/zod-openapi";
2
-
3
- // Minimal validation: only 'event' is essential. All other fields are optional
4
- // and extra fields pass through transparently (.passthrough()) so future changes
5
- // to the OpenCode hook format don't cause unexpected rejections.
6
- export const OpencodeWebhookBodySchema = z
7
- .object({
8
- event: z.string().openapi({ example: "session.status" }),
9
- sessionID: z.string().optional().openapi({ example: "ses_abc123" }),
10
- directory: z.string().optional().openapi({ example: "/home/user/project" }),
11
- title: z.string().optional().openapi({ example: "My session" }),
12
- permissionID: z.string().optional(),
13
- info: z
14
- .object({
15
- id: z.string().optional(),
16
- title: z.string().optional(),
17
- directory: z.string().optional(),
18
- parentID: z.string().optional(),
19
- time: z
20
- .object({
21
- created: z.number().optional(),
22
- updated: z.number().optional(),
23
- })
24
- .optional(),
25
- })
26
- .optional(),
27
- status: z.object({ type: z.string().optional() }).optional(),
28
- })
29
- .passthrough();
@@ -1,9 +0,0 @@
1
- export const STATE_BEARING_EVENTS = new Set([
2
- "SessionStart", "UserPromptSubmit", "PreToolUse", "PermissionRequest",
3
- "PermissionDenied", "PostToolUse", "PostToolUseFailure", "PostToolBatch",
4
- "Stop", "StopFailure", "SubagentStart", "SubagentStop", "SessionEnd",
5
- ]);
6
-
7
- export function isMetadataOnlyEvent(eventType: string): boolean {
8
- return !STATE_BEARING_EVENTS.has(eventType);
9
- }
@@ -1,8 +0,0 @@
1
- export const STATE_BEARING_EVENTS = new Set([
2
- "SessionStart", "UserPromptSubmit", "PreToolUse", "PermissionRequest",
3
- "PostToolUse", "SubagentStart", "SubagentStop", "Stop",
4
- ]);
5
-
6
- export function isMetadataOnlyEvent(eventType: string): boolean {
7
- return !STATE_BEARING_EVENTS.has(eventType);
8
- }
@@ -1,73 +0,0 @@
1
- import type { SessionModel } from "../../shared/types";
2
- import type { OpencodeWebhookPayload, HookStateResult } from "../models/opencode";
3
-
4
- export function mapHookState(
5
- event: string,
6
- payload: OpencodeWebhookPayload,
7
- existing?: SessionModel,
8
- ): HookStateResult {
9
- const prevCount = (existing?.opencode?.pendingAttentionCount ?? 0);
10
- let nextCount = prevCount;
11
-
12
- if (event === "permission.updated" || event === "permission.asked" || event === "question.asked") {
13
- nextCount = prevCount + 1;
14
- } else if (event === "permission.replied" || event === "question.replied") {
15
- nextCount = Math.max(0, prevCount - 1);
16
- }
17
-
18
- const hasAttention = nextCount > 0;
19
-
20
- if (event === "session.error") {
21
- return { state: "error", hasAttention: false, pendingAttentionCount: 0 };
22
- }
23
-
24
- if (hasAttention) {
25
- return { state: "waiting-input", hasAttention: true, pendingAttentionCount: nextCount };
26
- }
27
-
28
- if (event === "session.status" && payload.status?.type) {
29
- return {
30
- state: (payload.status.type === "busy" || payload.status.type === "retry") ? "working" : "idle",
31
- hasAttention: false,
32
- pendingAttentionCount: nextCount,
33
- };
34
- }
35
-
36
- if (event === "session.idle") {
37
- return { state: "idle", hasAttention: false, pendingAttentionCount: nextCount };
38
- }
39
-
40
- if (event === "permission.replied" || event === "question.replied") {
41
- return { state: existing?.state === "waiting-input" ? "idle" : (existing?.state ?? "idle"), hasAttention: false, pendingAttentionCount: nextCount };
42
- }
43
-
44
- return {
45
- state: existing?.state ?? "idle",
46
- hasAttention,
47
- pendingAttentionCount: nextCount,
48
- };
49
- }
50
-
51
- export function extractSessionParentId(
52
- event: string,
53
- payload: OpencodeWebhookPayload,
54
- existing?: SessionModel,
55
- ): string | undefined {
56
- if (event !== "session.created" && event !== "session.updated") {
57
- return existing?.parentSessionId;
58
- }
59
-
60
- const parentID = payload.info?.parentID;
61
- if (!parentID) {
62
- return existing?.parentSessionId;
63
- }
64
-
65
- // Only treat real session ids as parent sessions. Other event payloads may
66
- // include message-part ancestry such as msg_* ids, which would incorrectly
67
- // reclassify a root session as a hidden child row.
68
- if (!parentID.startsWith("ses_")) {
69
- return existing?.parentSessionId;
70
- }
71
-
72
- return `opencode:${parentID}`;
73
- }
package/src/classifier.ts DELETED
@@ -1,206 +0,0 @@
1
- import { detectTool, getPreviewMatchPositions, previewSignals } from "./detect";
2
- import type {
3
- DetectionReason,
4
- EnrichedPaneRecord,
5
- PaneModel,
6
- PaneSample,
7
- PaneState,
8
- ProcessInfo,
9
- SnapshotHistory,
10
- ToolKind,
11
- } from "./types";
12
- import { cleanSummaryText, compactWhitespace, normalizeCommandName, pickCompletionTail, pickMeaningfulTail, pickQuestionTail, pickSummaryTail } from "./shared/utils";
13
-
14
- const DEBOUNCE_THRESHOLD = 2;
15
-
16
- export function classifyPane(input: {
17
- pane: EnrichedPaneRecord;
18
- processTree: ProcessInfo[];
19
- sample: PaneSample | null;
20
- history?: SnapshotHistory;
21
- now: number;
22
- }): { pane: PaneModel; history: SnapshotHistory } {
23
- const { pane, processTree, sample, history, now } = input;
24
- const detectedTool = detectTool(pane.currentCommand, processTree);
25
- const rawState = detectState(detectedTool.tool, pane.currentCommand, processTree, sample);
26
-
27
- const fingerprint = [
28
- detectedTool.tool,
29
- rawState.state,
30
- processTree.map((process) => `${process.pid}:${process.comm}`).join(","),
31
- sample?.preview ?? "",
32
- ].join("|");
33
-
34
- const lastChangedAt = history?.lastChangedAt ?? now;
35
- const stableCount = history?.lastFingerprint === fingerprint ? (history.stableCount + 1) : 1;
36
-
37
- let tool: ToolKind = detectedTool.tool;
38
- let state: PaneState = rawState.state;
39
- let stateReasons = rawState.reasons;
40
- const shouldBypassDebounce = rawState.state === "working"
41
- && rawState.reasons.some((reason) => (
42
- reason.label === "activity keywords"
43
- || reason.label === "opencode footer activity"
44
- || reason.label === "active agent signals"
45
- ));
46
-
47
- if (history && stableCount < DEBOUNCE_THRESHOLD && !shouldBypassDebounce) {
48
- tool = history.tool;
49
- state = history.state;
50
- stateReasons = [
51
- {
52
- label: "debounced previous state",
53
- detail: `holding ${history.state} until new signal stabilizes`,
54
- },
55
- ...rawState.reasons,
56
- ];
57
- }
58
-
59
- const historyRecord: SnapshotHistory = {
60
- tool,
61
- state,
62
- lastFingerprint: fingerprint,
63
- lastChangedAt: ((stableCount === DEBOUNCE_THRESHOLD && history?.state !== state) || (shouldBypassDebounce && stableCount === 1)) ? now : lastChangedAt,
64
- stableCount,
65
- };
66
-
67
- const processChain = processTree.map((process) => compactWhitespace(process.comm || normalizeCommandName(process.command)));
68
- const summary = summarizeState(state, stateReasons, sample, processChain, pane.currentCommand);
69
-
70
- return {
71
- pane: {
72
- ...pane,
73
- processTree,
74
- processChain,
75
- sample,
76
- tool,
77
- state,
78
- toolReasons: detectedTool.reasons,
79
- stateReasons,
80
- summary,
81
- lastChangedAt: historyRecord.lastChangedAt,
82
- stableForMs: Math.max(0, now - historyRecord.lastChangedAt),
83
- hasAttention: state === "waiting-input",
84
- },
85
- history: historyRecord,
86
- };
87
- }
88
-
89
- export function detectState(tool: ToolKind, currentCommand: string, processTree: ProcessInfo[], sample: PaneSample | null): { state: PaneState; reasons: DetectionReason[] } {
90
- const reasons: DetectionReason[] = [];
91
- const promptSignals = previewSignals(sample);
92
- const previewMatches = getPreviewMatchPositions(sample);
93
- const processStates = processTree.map((process) => process.state).join(",");
94
- const commands = [currentCommand, ...processTree.map((process) => process.command)].join(" ");
95
- const meaningfulPreview = sample ? pickMeaningfulTail(sample.lines, sample.preview) : "";
96
-
97
- if (tool === "shell") {
98
- reasons.push({ label: "shell pane", detail: compactWhitespace(currentCommand || processTree[0]?.command || "shell") });
99
- return { state: "shell", reasons };
100
- }
101
-
102
- if (tool === "unknown") {
103
- reasons.push({ label: "insufficient classification signals", detail: compactWhitespace(commands) || "unknown workload" });
104
- return { state: "unknown", reasons };
105
- }
106
-
107
- const activityReason = promptSignals.find((reason) => reason.label === "activity keywords");
108
- const footerReason = promptSignals.find((reason) => reason.label === "opencode footer activity");
109
- const waitingReason = promptSignals.find((reason) => {
110
- if (reason.label !== "input prompt keywords" && reason.label !== "prompt-like tail") {
111
- return false;
112
- }
113
-
114
- return !/^>\s*implement\s*\{feature\}/i.test(reason.detail);
115
- });
116
- const completionReason = promptSignals.find((reason) => reason.label === "completion keywords");
117
- const activeKeywords = /(thinking|generating|executing|applying|running|streaming|analyzing|diff|patch|searching|reading)/i;
118
- const strongWorkingSignal = Boolean(
119
- (activityReason && /(working\s*\(|background terminal running|waited for background terminal|created\s+\d+)/i.test(activityReason.detail))
120
- || (tool === "opencode" && footerReason),
121
- );
122
- const hasRunnableProcessSignal = processTree.some((process) => /[RU]/.test(process.state));
123
- const hasActiveProcessSignal = hasRunnableProcessSignal;
124
- const hasFreshActivitySignal = Boolean(
125
- previewMatches.activityIndex >= Math.max(previewMatches.promptIndex, previewMatches.completionIndex)
126
- && (
127
- (activityReason && (cleanSummaryText(activityReason.detail) === meaningfulPreview || activeKeywords.test(meaningfulPreview)))
128
- || (tool === "opencode" && footerReason)
129
- ),
130
- );
131
-
132
- if (completionReason && !hasActiveProcessSignal && previewMatches.completionIndex >= previewMatches.activityIndex) {
133
- reasons.push({
134
- label: "completed work detected",
135
- detail: completionReason.detail,
136
- });
137
- return { state: waitingReason ? "waiting-input" : "idle", reasons: waitingReason ? [...reasons, waitingReason] : reasons };
138
- }
139
-
140
- if (strongWorkingSignal || hasFreshActivitySignal) {
141
- if (footerReason && tool === "opencode") {
142
- reasons.push(footerReason);
143
- } else if (activityReason) {
144
- reasons.push(activityReason);
145
- }
146
- return { state: "working", reasons };
147
- }
148
-
149
- if (waitingReason) {
150
- reasons.push(waitingReason);
151
- return { state: "waiting-input", reasons };
152
- }
153
-
154
- if (hasActiveProcessSignal) {
155
- reasons.push(footerReason ?? activityReason ?? { label: "active agent signals", detail: meaningfulPreview || processStates || compactWhitespace(commands) });
156
- return { state: "working", reasons };
157
- }
158
-
159
- reasons.push({ label: "agent present without active signals", detail: meaningfulPreview || compactWhitespace(commands) || tool });
160
- return { state: "idle", reasons };
161
- }
162
-
163
- function summarizeState(
164
- state: PaneState,
165
- stateReasons: DetectionReason[],
166
- sample: PaneSample | null,
167
- processChain: string[],
168
- currentCommand: string,
169
- ): string {
170
- const primaryReason = stateReasons.find((reason) => reason.label !== "debounced previous state")?.detail;
171
- const hasCompletionReason = stateReasons.some((reason) => reason.label === "completed work detected" || reason.label === "completion keywords");
172
- const preferredSummary = state === "waiting-input"
173
- ? pickQuestionTail(sample?.lines ?? [], primaryReason ?? "")
174
- : hasCompletionReason
175
- ? pickCompletionTail(sample?.lines ?? [], primaryReason ?? "")
176
- : pickSummaryTail(sample?.lines ?? [], primaryReason ?? "");
177
-
178
- if (primaryReason && primaryReason.length > 0 && primaryReason === preferredSummary) {
179
- return primaryReason;
180
- }
181
-
182
- if (sample?.lines.length) {
183
- const meaningful = state === "waiting-input"
184
- ? pickQuestionTail(sample.lines, sample.preview)
185
- : hasCompletionReason
186
- ? pickCompletionTail(sample.lines, sample.preview)
187
- : pickSummaryTail(sample.lines, sample.preview);
188
- if (meaningful) {
189
- return meaningful;
190
- }
191
- }
192
-
193
- if (sample?.preview) {
194
- return sample.preview;
195
- }
196
-
197
- if (processChain.length > 0) {
198
- return processChain.join(" -> ");
199
- }
200
-
201
- if (currentCommand) {
202
- return currentCommand;
203
- }
204
-
205
- return state;
206
- }