dot-studio 0.0.1 → 0.0.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 (148) hide show
  1. package/README.md +20 -200
  2. package/client/assets/ActFrame-BYOBkLYW.js +1 -0
  3. package/client/assets/ActFrame-C_WEt6bv.css +1 -0
  4. package/client/assets/ActInspectorPanel-C3VlS7tB.js +1 -0
  5. package/client/assets/ActInspectorPanel-CE6s6GYv.css +1 -0
  6. package/client/assets/AssistantChat-BOyW0K79.js +1 -0
  7. package/client/assets/AssistantChat-DoVmHvMJ.css +1 -0
  8. package/client/assets/CanvasTerminalFrame-BC-79q9U.css +1 -0
  9. package/client/assets/CanvasTerminalFrame-DxKbexK6.js +4 -0
  10. package/client/assets/CanvasTrackingFrame-DumxhNwg.js +1 -0
  11. package/client/assets/CanvasTrackingFrame-G4rRrfne.css +1 -0
  12. package/client/assets/CanvasWindowFrame-ziJeVfHG.js +1 -0
  13. package/client/assets/DanceBundleEditorFrame-CH8VDUMK.js +1 -0
  14. package/client/assets/DanceBundleEditorFrame-DaLqMflT.css +1 -0
  15. package/client/assets/MarkdownEditorFrame-DVecIZpZ.css +1 -0
  16. package/client/assets/MarkdownEditorFrame-Dwpgs2GX.js +2 -0
  17. package/client/assets/MarkdownRenderer-Cz8A4AgP.js +1 -0
  18. package/client/assets/PublishModal-DUlHz0fT.js +1 -0
  19. package/client/assets/TodoDock-DcVf7zQG.js +1 -0
  20. package/client/assets/WorkspaceToolbar-CXYi_sMD.js +2 -0
  21. package/client/assets/WorkspaceToolbar-CiQvVocC.css +1 -0
  22. package/client/assets/chat-message-visibility-YwJ-AQno.js +11 -0
  23. package/client/assets/dnd-vendor-CIAZE2P2.js +5 -0
  24. package/client/assets/flow-vendor-BZV40eAE.css +1 -0
  25. package/client/assets/flow-vendor-C868rU-6.js +23 -0
  26. package/client/assets/icon-vendor-I2JVIi1s.js +501 -0
  27. package/client/assets/index-BMY4hrBP.js +3 -0
  28. package/client/assets/index-C-vnj9y3.js +1 -0
  29. package/client/assets/index-C9HTqfZw.css +1 -0
  30. package/client/assets/index-CWrv6O3o.js +64 -0
  31. package/client/assets/index-DMS12-Q2.js +8 -0
  32. package/client/assets/index-Dn7t_Y7G.js +1 -0
  33. package/client/assets/index-p-wk7iGH.css +1 -0
  34. package/client/assets/markdown-vendor-BSTcku12.css +10 -0
  35. package/client/assets/markdown-vendor-DnTJ9hmR.js +35 -0
  36. package/client/assets/participant-labels-Cf3qP3GB.js +1 -0
  37. package/client/assets/queries-Dm1jEHfc.js +1 -0
  38. package/client/assets/query-vendor-_taqgrbn.js +1 -0
  39. package/client/assets/react-vendor-DzpMUNDT.js +49 -0
  40. package/client/assets/settings-utils-l7KCS3Ev.js +1 -0
  41. package/client/assets/terminal-vendor-6GBZ9nXN.css +32 -0
  42. package/client/assets/terminal-vendor-D0xRnmbI.js +112 -0
  43. package/client/index.html +13 -3
  44. package/dist/cli.js +25 -3
  45. package/dist/server/app.js +72 -0
  46. package/dist/server/index.js +2 -62
  47. package/dist/server/lib/act-session-policy.js +31 -0
  48. package/dist/server/lib/chat-session.js +101 -0
  49. package/dist/server/lib/config.js +18 -4
  50. package/dist/server/lib/dot-authoring.js +171 -102
  51. package/dist/server/lib/dot-loader.js +9 -8
  52. package/dist/server/lib/dot-login.js +8 -190
  53. package/dist/server/lib/dot-source.js +11 -0
  54. package/dist/server/lib/model-catalog.js +74 -15
  55. package/dist/server/lib/opencode-auth.js +4 -1
  56. package/dist/server/lib/opencode-errors.js +70 -38
  57. package/dist/server/lib/opencode-sidecar.js +5 -2
  58. package/dist/server/lib/project-config.js +8 -0
  59. package/dist/server/lib/runtime-tools.js +46 -8
  60. package/dist/server/lib/safe-mode.js +410 -0
  61. package/dist/server/lib/session-execution.js +81 -0
  62. package/dist/server/lib/sse.js +22 -0
  63. package/dist/server/routes/act-runtime-threads.js +156 -0
  64. package/dist/server/routes/act-runtime-tools.js +157 -0
  65. package/dist/server/routes/act-runtime.js +7 -0
  66. package/dist/server/routes/adapter.js +32 -0
  67. package/dist/server/routes/assets-collection.js +16 -0
  68. package/dist/server/routes/assets-detail.js +38 -0
  69. package/dist/server/routes/assets.js +4 -158
  70. package/dist/server/routes/chat-messages.js +104 -0
  71. package/dist/server/routes/chat-sessions.js +104 -0
  72. package/dist/server/routes/chat-stream.js +15 -0
  73. package/dist/server/routes/chat.js +6 -353
  74. package/dist/server/routes/compile.js +5 -91
  75. package/dist/server/routes/dot-assets.js +77 -0
  76. package/dist/server/routes/dot-core.js +62 -0
  77. package/dist/server/routes/dot-performer.js +80 -0
  78. package/dist/server/routes/dot.js +6 -267
  79. package/dist/server/routes/drafts-collection.js +40 -0
  80. package/dist/server/routes/drafts-dance-bundle.js +113 -0
  81. package/dist/server/routes/drafts-item.js +86 -0
  82. package/dist/server/routes/drafts.js +9 -0
  83. package/dist/server/routes/health.js +18 -33
  84. package/dist/server/routes/opencode-core.js +120 -0
  85. package/dist/server/routes/opencode-file.js +67 -0
  86. package/dist/server/routes/opencode-mcp.js +74 -0
  87. package/dist/server/routes/opencode-provider.js +41 -0
  88. package/dist/server/routes/opencode.js +8 -418
  89. package/dist/server/routes/route-errors.js +10 -0
  90. package/dist/server/routes/safe-actions.js +60 -0
  91. package/dist/server/routes/safe-summary.js +20 -0
  92. package/dist/server/routes/safe.js +7 -0
  93. package/dist/server/routes/workspaces.js +47 -0
  94. package/dist/server/services/act-runtime/act-context-builder.js +81 -0
  95. package/dist/server/services/act-runtime/act-runtime-service.js +313 -0
  96. package/dist/server/services/act-runtime/act-runtime-utils.js +10 -0
  97. package/dist/server/services/act-runtime/act-tool-projection.js +26 -0
  98. package/dist/server/services/act-runtime/act-tools.js +151 -0
  99. package/dist/server/services/act-runtime/board-persistence.js +38 -0
  100. package/dist/server/services/act-runtime/event-logger.js +73 -0
  101. package/dist/server/services/act-runtime/event-router.js +102 -0
  102. package/dist/server/services/act-runtime/mailbox.js +149 -0
  103. package/dist/server/services/act-runtime/safety-guard.js +162 -0
  104. package/dist/server/services/act-runtime/session-queue.js +114 -0
  105. package/dist/server/services/act-runtime/thread-manager.js +351 -0
  106. package/dist/server/services/act-runtime/wake-cascade.js +306 -0
  107. package/dist/server/services/act-runtime/wake-evaluator.js +43 -0
  108. package/dist/server/services/act-runtime/wake-performer-resolver.js +68 -0
  109. package/dist/server/services/act-runtime/wake-prompt-builder.js +77 -0
  110. package/dist/server/services/adapter-view-service.js +6 -0
  111. package/dist/server/services/asset-service.js +366 -0
  112. package/dist/server/services/chat-event-stream-service.js +157 -0
  113. package/dist/server/services/chat-service.js +207 -0
  114. package/dist/server/services/chat-session-service.js +203 -0
  115. package/dist/server/services/compile-service.js +4 -0
  116. package/dist/server/services/dance-bundle-service.js +222 -0
  117. package/dist/server/services/dot-add-service.js +59 -0
  118. package/dist/server/services/dot-service.js +178 -0
  119. package/dist/server/services/draft-service.js +367 -0
  120. package/dist/server/services/opencode-projection/dance-compiler.js +164 -0
  121. package/dist/server/services/opencode-projection/performer-compiler.js +195 -0
  122. package/dist/server/services/opencode-projection/preview-service.js +31 -0
  123. package/dist/server/services/opencode-projection/projection-manifest.js +98 -0
  124. package/dist/server/services/opencode-projection/stage-projection-service.js +188 -0
  125. package/dist/server/services/opencode-service.js +338 -0
  126. package/dist/server/services/safe-service.js +33 -0
  127. package/dist/server/services/studio-assistant/assistant-service.js +172 -0
  128. package/dist/server/services/studio-service.js +69 -0
  129. package/dist/server/services/workspace-service.js +224 -0
  130. package/dist/server/terminal.js +57 -11
  131. package/dist/shared/act-types.js +4 -0
  132. package/dist/shared/adapter-view.js +1 -0
  133. package/dist/shared/asset-contracts.js +1 -0
  134. package/dist/shared/assistant-actions.js +1 -0
  135. package/dist/shared/chat-contracts.js +1 -0
  136. package/dist/shared/dot-contracts.js +1 -0
  137. package/dist/shared/dot-types.js +4 -0
  138. package/dist/shared/draft-contracts.js +2 -0
  139. package/dist/shared/model-types.js +2 -0
  140. package/dist/shared/performer-mcp-portability.js +10 -0
  141. package/dist/shared/safe-mode.js +1 -0
  142. package/dist/shared/session-metadata.js +4 -3
  143. package/package.json +6 -4
  144. package/client/assets/index-C2eIILoa.css +0 -41
  145. package/client/assets/index-DUPZ_Lw5.js +0 -616
  146. package/dist/server/lib/act-runtime.js +0 -1282
  147. package/dist/server/lib/prompt.js +0 -222
  148. package/dist/server/routes/stages.js +0 -137
@@ -0,0 +1,157 @@
1
+ import { Hono } from 'hono';
2
+ import { parseActSessionOwnerId, resolveSessionExecutionContext } from '../lib/session-execution.js';
3
+ import { getActDefinitionForThread, getActRuntimeService } from '../services/act-runtime/act-runtime-service.js';
4
+ import { requestWorkingDir } from './route-errors.js';
5
+ const actRuntimeTools = new Hono();
6
+ async function resolveActSessionTarget(sessionId) {
7
+ const context = await resolveSessionExecutionContext(sessionId);
8
+ if (!context || context.ownerKind !== 'act') {
9
+ return null;
10
+ }
11
+ const parsed = parseActSessionOwnerId(context.ownerId);
12
+ if (!parsed) {
13
+ return null;
14
+ }
15
+ return {
16
+ workingDir: context.workingDir,
17
+ threadId: parsed.threadId,
18
+ participantKey: parsed.participantKey,
19
+ };
20
+ }
21
+ async function resolveParticipantKeyByName(workingDir, threadId, recipient) {
22
+ const actDefinition = await getActDefinitionForThread(workingDir, threadId);
23
+ if (!actDefinition) {
24
+ return recipient;
25
+ }
26
+ const normalizedRecipient = recipient.trim().toLowerCase();
27
+ for (const [participantKey, binding] of Object.entries(actDefinition.participants || {})) {
28
+ const displayName = (binding.displayName || participantKey).trim().toLowerCase();
29
+ if (displayName === normalizedRecipient || participantKey.toLowerCase() === normalizedRecipient) {
30
+ return participantKey;
31
+ }
32
+ }
33
+ return null;
34
+ }
35
+ // ── Debug logging middleware for Act tool routes ────
36
+ actRuntimeTools.use('/api/act/*', async (c, next) => {
37
+ const url = c.req.url;
38
+ const method = c.req.method;
39
+ const workingDir = c.req.query('workingDir') || c.req.header('x-dot-working-dir') || 'NONE';
40
+ console.log(`[act-tool-req] ${method} ${url.replace(/\?.*/, '')} workingDir=${decodeURIComponent(workingDir).slice(-40)}`);
41
+ await next();
42
+ });
43
+ actRuntimeTools.post('/api/act/:actId/thread/:threadId/send-message', async (c) => {
44
+ const threadId = c.req.param('threadId');
45
+ const workingDir = requestWorkingDir(c);
46
+ console.log(`[act-tool] send-message: threadId=${threadId}, workingDir=${workingDir}`);
47
+ const body = await c.req.json();
48
+ const service = getActRuntimeService(workingDir);
49
+ const result = await service.sendMessage(threadId, body);
50
+ console.log(`[act-tool] send-message result: ok=${result.ok}${!result.ok ? `, error=${result.error}` : ''}`);
51
+ if (!result.ok) {
52
+ return c.json(result, result.status);
53
+ }
54
+ return c.json(result);
55
+ });
56
+ actRuntimeTools.get('/api/act/session/:sessionId/read-shared-board', async (c) => {
57
+ const target = await resolveActSessionTarget(c.req.param('sessionId'));
58
+ if (!target) {
59
+ return c.json({ ok: false, error: 'Act session not found' }, 404);
60
+ }
61
+ const key = c.req.query('key');
62
+ const result = await getActRuntimeService(target.workingDir).readBoard(target.threadId, key);
63
+ if (!result.ok) {
64
+ return c.json(result, result.status);
65
+ }
66
+ return c.json(result);
67
+ });
68
+ actRuntimeTools.post('/api/act/session/:sessionId/message-teammate', async (c) => {
69
+ const target = await resolveActSessionTarget(c.req.param('sessionId'));
70
+ if (!target) {
71
+ return c.json({ ok: false, error: 'Act session not found' }, 404);
72
+ }
73
+ const body = await c.req.json();
74
+ const recipientKey = await resolveParticipantKeyByName(target.workingDir, target.threadId, body.recipient);
75
+ if (!recipientKey) {
76
+ return c.json({ ok: false, error: `Unknown teammate "${body.recipient}"` }, 400);
77
+ }
78
+ const result = await getActRuntimeService(target.workingDir).sendMessage(target.threadId, {
79
+ from: target.participantKey,
80
+ to: recipientKey,
81
+ content: body.message,
82
+ tag: body.tag,
83
+ });
84
+ if (!result.ok) {
85
+ return c.json(result, result.status);
86
+ }
87
+ return c.json(result);
88
+ });
89
+ actRuntimeTools.post('/api/act/:actId/thread/:threadId/post-to-board', async (c) => {
90
+ const threadId = c.req.param('threadId');
91
+ const workingDir = requestWorkingDir(c);
92
+ console.log(`[act-tool] post-to-board: threadId=${threadId}, workingDir=${workingDir}`);
93
+ const body = await c.req.json();
94
+ const result = await getActRuntimeService(workingDir).postToBoard(threadId, body);
95
+ console.log(`[act-tool] post-to-board result: ok=${result.ok}${!result.ok ? `, error=${result.error}` : ''}`);
96
+ if (!result.ok) {
97
+ return c.json(result, result.status);
98
+ }
99
+ return c.json(result);
100
+ });
101
+ actRuntimeTools.post('/api/act/session/:sessionId/update-shared-board', async (c) => {
102
+ const target = await resolveActSessionTarget(c.req.param('sessionId'));
103
+ if (!target) {
104
+ return c.json({ ok: false, error: 'Act session not found' }, 404);
105
+ }
106
+ const body = await c.req.json();
107
+ const result = await getActRuntimeService(target.workingDir).postToBoard(target.threadId, {
108
+ author: target.participantKey,
109
+ key: body.entryKey,
110
+ kind: body.entryType,
111
+ content: body.content,
112
+ updateMode: body.mode,
113
+ });
114
+ if (!result.ok) {
115
+ return c.json(result, result.status);
116
+ }
117
+ return c.json(result);
118
+ });
119
+ actRuntimeTools.get('/api/act/:actId/thread/:threadId/read-board', async (c) => {
120
+ const threadId = c.req.param('threadId');
121
+ const workingDir = requestWorkingDir(c);
122
+ console.log(`[act-tool] read-board: threadId=${threadId}, workingDir=${workingDir}`);
123
+ const key = c.req.query('key');
124
+ const result = await getActRuntimeService(workingDir).readBoard(threadId, key);
125
+ console.log(`[act-tool] read-board result: ok=${result.ok}${!result.ok ? `, error=${result.error}` : ''}`);
126
+ if (!result.ok) {
127
+ return c.json(result, result.status);
128
+ }
129
+ return c.json(result);
130
+ });
131
+ actRuntimeTools.post('/api/act/:actId/thread/:threadId/set-wake-condition', async (c) => {
132
+ const threadId = c.req.param('threadId');
133
+ const body = await c.req.json();
134
+ const result = await getActRuntimeService(requestWorkingDir(c)).setWakeCondition(threadId, body);
135
+ if (!result.ok) {
136
+ return c.json(result, result.status);
137
+ }
138
+ return c.json(result);
139
+ });
140
+ actRuntimeTools.post('/api/act/session/:sessionId/wait-until', async (c) => {
141
+ const target = await resolveActSessionTarget(c.req.param('sessionId'));
142
+ if (!target) {
143
+ return c.json({ ok: false, error: 'Act session not found' }, 404);
144
+ }
145
+ const body = await c.req.json();
146
+ const result = await getActRuntimeService(target.workingDir).setWakeCondition(target.threadId, {
147
+ createdBy: target.participantKey,
148
+ target: 'self',
149
+ onSatisfiedMessage: body.resumeWith,
150
+ condition: body.condition,
151
+ });
152
+ if (!result.ok) {
153
+ return c.json(result, result.status);
154
+ }
155
+ return c.json(result);
156
+ });
157
+ export default actRuntimeTools;
@@ -0,0 +1,7 @@
1
+ import { Hono } from 'hono';
2
+ import actRuntimeTools from './act-runtime-tools.js';
3
+ import actRuntimeThreads from './act-runtime-threads.js';
4
+ const actRuntime = new Hono();
5
+ actRuntime.route('/', actRuntimeTools);
6
+ actRuntime.route('/', actRuntimeThreads);
7
+ export default actRuntime;
@@ -0,0 +1,32 @@
1
+ import { Hono } from 'hono';
2
+ import { dispatchAdapterViewAction, listAdapterViewProjections } from '../services/adapter-view-service.js';
3
+ import { createSSEResponse } from '../lib/sse.js';
4
+ const adapter = new Hono();
5
+ function errorMessage(error) {
6
+ return error instanceof Error ? error.message : 'Adapter action is not available yet.';
7
+ }
8
+ adapter.get('/api/adapter/views', async (c) => {
9
+ const performerId = c.req.query('performerId')?.trim();
10
+ const projections = await listAdapterViewProjections(performerId || undefined);
11
+ return c.json({ projections });
12
+ });
13
+ adapter.post('/api/adapter/action', async (c) => {
14
+ try {
15
+ const body = await c.req.json();
16
+ const result = await dispatchAdapterViewAction(body);
17
+ return c.json(result);
18
+ }
19
+ catch (error) {
20
+ return c.json({ error: errorMessage(error) }, 501);
21
+ }
22
+ });
23
+ adapter.get('/api/adapter/events', async () => {
24
+ const stream = new ReadableStream({
25
+ start(controller) {
26
+ const encoder = new TextEncoder();
27
+ controller.enqueue(encoder.encode(': adapter stream placeholder\n\n'));
28
+ },
29
+ });
30
+ return createSSEResponse(stream);
31
+ });
32
+ export default adapter;
@@ -0,0 +1,16 @@
1
+ import { Hono } from 'hono';
2
+ import { cached, TTL } from '../lib/cache.js';
3
+ import { listStudioAssets } from '../services/asset-service.js';
4
+ import { jsonError, requestWorkingDir } from './route-errors.js';
5
+ const assetsCollection = new Hono();
6
+ assetsCollection.get('/api/assets/:kind', async (c) => {
7
+ const kind = c.req.param('kind');
8
+ if (!['tal', 'dance', 'performer', 'act'].includes(kind)) {
9
+ return jsonError(c, `Invalid kind: ${kind}`, 400);
10
+ }
11
+ const cwd = requestWorkingDir(c);
12
+ const assetKind = kind;
13
+ const assetList = await cached(`assets-${assetKind}-${cwd}`, TTL.ASSETS, () => listStudioAssets(cwd, assetKind));
14
+ return c.json(assetList);
15
+ });
16
+ export default assetsCollection;
@@ -0,0 +1,38 @@
1
+ import { Hono } from 'hono';
2
+ import { cached, TTL } from '../lib/cache.js';
3
+ import { getRegistryAssetDetail, getStudioAsset } from '../services/asset-service.js';
4
+ import { jsonError, requestWorkingDir } from './route-errors.js';
5
+ const assetsDetail = new Hono();
6
+ function errorMessage(error) {
7
+ return error instanceof Error ? error.message : 'Unknown error';
8
+ }
9
+ function assetPathFromRequest(c) {
10
+ return c.req.query('path') || c.req.param('name') || '';
11
+ }
12
+ async function handleStudioAssetDetail(c) {
13
+ const { kind, author } = c.req.param();
14
+ const assetPath = assetPathFromRequest(c);
15
+ try {
16
+ return c.json(await getStudioAsset(requestWorkingDir(c), kind, author, assetPath));
17
+ }
18
+ catch (error) {
19
+ return jsonError(c, errorMessage(error), 404);
20
+ }
21
+ }
22
+ async function handleRegistryAssetDetail(c) {
23
+ const { kind, author } = c.req.param();
24
+ const assetPath = assetPathFromRequest(c);
25
+ const cwd = requestWorkingDir(c);
26
+ try {
27
+ const detail = await cached(`registry-asset-${cwd}-${kind}-${author}-${assetPath}`, TTL.PROVIDERS, () => getRegistryAssetDetail(cwd, kind, author, assetPath));
28
+ return c.json(detail);
29
+ }
30
+ catch (error) {
31
+ return jsonError(c, errorMessage(error), 404);
32
+ }
33
+ }
34
+ assetsDetail.get('/api/assets/:kind/:author', handleStudioAssetDetail);
35
+ assetsDetail.get('/api/assets/:kind/:author/:name', handleStudioAssetDetail);
36
+ assetsDetail.get('/api/assets/registry/:kind/:author', handleRegistryAssetDetail);
37
+ assetsDetail.get('/api/assets/registry/:kind/:author/:name', handleRegistryAssetDetail);
38
+ export default assetsDetail;
@@ -1,161 +1,7 @@
1
- // Asset Routes — using DOT lib directly
2
1
  import { Hono } from 'hono';
3
- import fs from 'fs/promises';
4
- import path from 'path';
5
- import { assetFilePath, getDotDir, getGlobalCwd, getGlobalDotDir, readAsset } from 'dance-of-tal/lib/registry';
6
- import { getRegistryPackage } from 'dance-of-tal/lib/installer';
7
- import { cached, TTL } from '../lib/cache.js';
8
- import { resolveRequestWorkingDir } from '../lib/request-context.js';
2
+ import assetsCollection from './assets-collection.js';
3
+ import assetsDetail from './assets-detail.js';
9
4
  const assets = new Hono();
10
- // ── Asset Scanning ──────────────────────────────────────
11
- // Scans both local (.dance-of-tal/) and global (~/.dance-of-tal/) dirs
12
- // Each asset is tagged with source: 'global' | 'stage'
13
- // Stage assets override global for the same URN
14
- function normalizeAsset(kind, urn, author, source, content, detail = false) {
15
- const slug = urn.split('/')[2];
16
- const normalizedAuthor = author.startsWith('@') ? author : `@${author}`;
17
- const base = {
18
- kind,
19
- urn,
20
- slug,
21
- name: typeof content.name === 'string' && content.name.trim() ? content.name.trim() : slug,
22
- author: normalizedAuthor,
23
- source,
24
- description: typeof content.description === 'string' ? content.description : '',
25
- };
26
- if (kind === 'performer') {
27
- const danceRaw = content.dance;
28
- const danceUrns = Array.isArray(danceRaw)
29
- ? danceRaw.filter((value) => typeof value === 'string')
30
- : typeof danceRaw === 'string'
31
- ? [danceRaw]
32
- : [];
33
- return {
34
- ...base,
35
- talUrn: typeof content.tal === 'string' ? content.tal : null,
36
- danceUrns,
37
- actUrn: typeof content.act === 'string' ? content.act : null,
38
- model: typeof content.model === 'string' ? content.model : null,
39
- mcpConfig: typeof content.mcp_config === 'object' && content.mcp_config !== null ? content.mcp_config : null,
40
- tags: Array.isArray(content.tags) ? content.tags : [],
41
- };
42
- }
43
- if (kind === 'act') {
44
- return {
45
- ...base,
46
- entryNode: typeof content.entryNode === 'string' ? content.entryNode : null,
47
- nodeCount: typeof content.nodes === 'object' && content.nodes ? Object.keys(content.nodes).length : 0,
48
- tags: Array.isArray(content.tags) ? content.tags : [],
49
- ...(detail ? {
50
- nodes: typeof content.nodes === 'object' && content.nodes ? content.nodes : {},
51
- edges: Array.isArray(content.edges) ? content.edges : [],
52
- maxIterations: typeof content.maxIterations === 'number' ? content.maxIterations : undefined,
53
- } : {}),
54
- };
55
- }
56
- return {
57
- ...base,
58
- tags: Array.isArray(content.tags) ? content.tags : [],
59
- ...(detail && typeof content.content === 'string' ? { content: content.content } : {}),
60
- };
61
- }
62
- async function fetchRegistryAsset(kind, author, name) {
63
- const pkg = await getRegistryPackage(kind, author, name);
64
- const urn = typeof pkg.urn === 'string' && pkg.urn ? pkg.urn : `${kind}/@${author.replace(/^@/, '')}/${name}`;
65
- const normalized = normalizeAsset(kind, urn, `@${author.replace(/^@/, '')}`, 'registry', pkg.payload, true);
66
- return {
67
- ...normalized,
68
- stars: typeof pkg.stars === 'number' ? pkg.stars : 0,
69
- tier: typeof pkg.tier === 'string' ? pkg.tier : undefined,
70
- updatedAt: typeof pkg.updatedAt === 'string' ? pkg.updatedAt : undefined,
71
- };
72
- }
73
- async function scanAssetDir(baseDir, kind, source, resultsMap) {
74
- const kindDir = path.join(baseDir, kind);
75
- try {
76
- const authors = await fs.readdir(kindDir);
77
- for (const author of authors) {
78
- if (!author.startsWith('@'))
79
- continue;
80
- const authorDir = path.join(kindDir, author);
81
- const stat = await fs.stat(authorDir);
82
- if (!stat.isDirectory())
83
- continue;
84
- const files = await fs.readdir(authorDir);
85
- for (const file of files) {
86
- if (!file.endsWith('.json'))
87
- continue;
88
- try {
89
- const content = JSON.parse(await fs.readFile(path.join(authorDir, file), 'utf-8'));
90
- const name = file.replace(/\.json$/, '');
91
- const urn = `${kind}/${author}/${name}`;
92
- resultsMap.set(urn, normalizeAsset(kind, urn, author, source, content, false));
93
- }
94
- catch { /* skip invalid files */ }
95
- }
96
- }
97
- }
98
- catch { /* directory doesn't exist */ }
99
- }
100
- async function listAssets(cwd, kind) {
101
- const resultsMap = new Map();
102
- const globalDir = getGlobalDotDir();
103
- const localDir = getDotDir(cwd);
104
- // 1. Scan Global Directory (Base)
105
- await scanAssetDir(globalDir, kind, 'global', resultsMap);
106
- // 2. Scan Stage Directory (Overrides)
107
- await scanAssetDir(localDir, kind, 'stage', resultsMap);
108
- return Array.from(resultsMap.values());
109
- }
110
- async function resolveAssetSource(cwd, urn) {
111
- try {
112
- await fs.access(assetFilePath(cwd, urn));
113
- return 'stage';
114
- }
115
- catch {
116
- try {
117
- await fs.access(assetFilePath(getGlobalCwd(), urn));
118
- return 'global';
119
- }
120
- catch {
121
- return 'stage';
122
- }
123
- }
124
- }
125
- // ── Routes ──────────────────────────────────────────────
126
- assets.get('/api/assets/:kind', async (c) => {
127
- const kind = c.req.param('kind');
128
- if (!['tal', 'dance', 'performer', 'act'].includes(kind)) {
129
- return c.json({ error: `Invalid kind: ${kind}` }, 400);
130
- }
131
- const cwd = resolveRequestWorkingDir(c);
132
- const assetList = await cached(`assets-${kind}-${cwd}`, TTL.ASSETS, () => listAssets(cwd, kind));
133
- return c.json(assetList);
134
- });
135
- assets.get('/api/assets/:kind/:author/:name', async (c) => {
136
- const { kind, author, name } = c.req.param();
137
- const urn = `${kind}/@${author}/${name}`;
138
- try {
139
- const cwd = resolveRequestWorkingDir(c);
140
- const asset = await readAsset(cwd, urn);
141
- if (!asset) {
142
- return c.json({ error: `Asset not found: ${urn}` }, 404);
143
- }
144
- const source = await resolveAssetSource(cwd, urn);
145
- return c.json(normalizeAsset(kind, urn, `@${author}`, source, asset, true));
146
- }
147
- catch (err) {
148
- return c.json({ error: err.message }, 404);
149
- }
150
- });
151
- assets.get('/api/assets/registry/:kind/:author/:name', async (c) => {
152
- const { kind, author, name } = c.req.param();
153
- try {
154
- const detail = await cached(`registry-asset-${kind}-${author}-${name}`, TTL.PROVIDERS, () => fetchRegistryAsset(kind, author, name));
155
- return c.json(detail);
156
- }
157
- catch (err) {
158
- return c.json({ error: err.message }, 404);
159
- }
160
- });
5
+ assets.route('/', assetsCollection);
6
+ assets.route('/', assetsDetail);
161
7
  export default assets;
@@ -0,0 +1,104 @@
1
+ import { Hono } from 'hono';
2
+ import { uniqueAssetRefs } from '../lib/chat-session.js';
3
+ import { resolveSessionExecutionContext } from '../lib/session-execution.js';
4
+ import { StudioValidationError, jsonOpencodeError, } from '../lib/opencode-errors.js';
5
+ import { sendStudioChatMessage } from '../services/chat-service.js';
6
+ import { listStudioSessionDiff, listStudioSessionMessages, rejectQuestion, respondQuestion, } from '../services/chat-session-service.js';
7
+ import { requestWorkingDir } from './route-errors.js';
8
+ const chatMessages = new Hono();
9
+ function parseMessagesQuery(c) {
10
+ const rawLimit = c.req.query('limit');
11
+ const rawBefore = c.req.query('before');
12
+ let limit;
13
+ if (typeof rawLimit === 'string' && rawLimit.trim().length > 0) {
14
+ const parsed = Number.parseInt(rawLimit, 10);
15
+ if (!Number.isFinite(parsed) || parsed <= 0) {
16
+ throw new StudioValidationError('Query "limit" must be a positive integer.', 'fix_input');
17
+ }
18
+ limit = parsed;
19
+ }
20
+ const before = typeof rawBefore === 'string' && rawBefore.trim().length > 0
21
+ ? rawBefore.trim()
22
+ : undefined;
23
+ return {
24
+ limit,
25
+ before,
26
+ };
27
+ }
28
+ chatMessages.post('/api/chat/sessions/:id/send', async (c) => {
29
+ const body = await c.req.json();
30
+ if (!body.performer?.model) {
31
+ return jsonOpencodeError(c, new StudioValidationError('Select a model for this performer before sending prompts.', 'select_model'));
32
+ }
33
+ try {
34
+ const workingDir = requestWorkingDir(c);
35
+ const executionDir = (await resolveSessionExecutionContext(c.req.param('id')))?.executionDir || workingDir;
36
+ const normalizedBody = {
37
+ ...body,
38
+ performer: {
39
+ ...body.performer,
40
+ danceRefs: uniqueAssetRefs(body.performer?.danceRefs || []),
41
+ extraDanceRefs: uniqueAssetRefs(body.performer?.extraDanceRefs || []),
42
+ },
43
+ };
44
+ const result = await sendStudioChatMessage(executionDir, workingDir, c.req.param('id'), normalizedBody);
45
+ return c.json(result, 202);
46
+ }
47
+ catch (err) {
48
+ return jsonOpencodeError(c, err, { model: body.performer?.model });
49
+ }
50
+ });
51
+ chatMessages.post('/api/chat/questions/:qid/respond', async (c) => {
52
+ const { answers } = await c.req.json();
53
+ try {
54
+ return c.json(await respondQuestion(c.req.param('qid'), answers));
55
+ }
56
+ catch (err) {
57
+ return jsonOpencodeError(c, err);
58
+ }
59
+ });
60
+ chatMessages.post('/api/chat/questions/:qid/reject', async (c) => {
61
+ try {
62
+ return c.json(await rejectQuestion(c.req.param('qid')));
63
+ }
64
+ catch (err) {
65
+ return jsonOpencodeError(c, err);
66
+ }
67
+ });
68
+ chatMessages.get('/api/chat/sessions/:id/messages', async (c) => {
69
+ try {
70
+ const query = parseMessagesQuery(c);
71
+ const result = await listStudioSessionMessages(requestWorkingDir(c), c.req.param('id'), query);
72
+ c.header('x-next-cursor', result.nextCursor || '');
73
+ return c.json(result.messages);
74
+ }
75
+ catch (err) {
76
+ return jsonOpencodeError(c, err);
77
+ }
78
+ });
79
+ chatMessages.get('/api/chat/sessions/:id/diff', async (c) => {
80
+ try {
81
+ return c.json(await listStudioSessionDiff(requestWorkingDir(c), c.req.param('id')));
82
+ }
83
+ catch (err) {
84
+ return jsonOpencodeError(c, err);
85
+ }
86
+ });
87
+ /**
88
+ * Resolve a session ID to its owner info (chatKey / performerId).
89
+ * Used by the frontend to lazily register sessions created externally
90
+ * (e.g., by wake cascade) into the sessionMap.
91
+ */
92
+ chatMessages.get('/api/chat/sessions/:id/resolve', async (c) => {
93
+ const context = await resolveSessionExecutionContext(c.req.param('id'));
94
+ if (!context) {
95
+ return c.json({ found: false }, 404);
96
+ }
97
+ return c.json({
98
+ found: true,
99
+ sessionId: context.sessionId,
100
+ ownerId: context.ownerId,
101
+ ownerKind: context.ownerKind,
102
+ });
103
+ });
104
+ export default chatMessages;
@@ -0,0 +1,104 @@
1
+ import { Hono } from 'hono';
2
+ import { StudioValidationError, jsonOpencodeError, } from '../lib/opencode-errors.js';
3
+ import { createStudioChatSession } from '../services/chat-service.js';
4
+ import { abortStudioChatSession, deleteStudioChatSession, listStudioChatSessions, renameStudioChatSession, respondSessionPermission, revertStudioChatSession, shareStudioChatSession, summarizeStudioChatSession, unrevertStudioChatSession, listPendingPermissions, } from '../services/chat-session-service.js';
5
+ import { requestWorkingDir } from './route-errors.js';
6
+ const chatSessions = new Hono();
7
+ chatSessions.post('/api/chat/sessions', async (c) => {
8
+ const body = await c.req.json();
9
+ try {
10
+ return c.json(await createStudioChatSession(requestWorkingDir(c), body));
11
+ }
12
+ catch (err) {
13
+ return jsonOpencodeError(c, err);
14
+ }
15
+ });
16
+ chatSessions.get('/api/chat/sessions', async (c) => {
17
+ try {
18
+ return c.json(await listStudioChatSessions(requestWorkingDir(c)));
19
+ }
20
+ catch (err) {
21
+ return jsonOpencodeError(c, err);
22
+ }
23
+ });
24
+ chatSessions.delete('/api/chat/sessions/:id', async (c) => {
25
+ try {
26
+ return c.json(await deleteStudioChatSession(requestWorkingDir(c), c.req.param('id')));
27
+ }
28
+ catch (err) {
29
+ return jsonOpencodeError(c, err);
30
+ }
31
+ });
32
+ chatSessions.put('/api/chat/sessions/:id', async (c) => {
33
+ const body = await c.req.json().catch(() => ({}));
34
+ const title = body.title;
35
+ if (!title || !title.trim()) {
36
+ return jsonOpencodeError(c, new StudioValidationError('Thread title is required.', 'fix_input'));
37
+ }
38
+ try {
39
+ return c.json(await renameStudioChatSession(requestWorkingDir(c), c.req.param('id'), title.trim()));
40
+ }
41
+ catch (err) {
42
+ return jsonOpencodeError(c, err);
43
+ }
44
+ });
45
+ chatSessions.post('/api/chat/sessions/:id/abort', async (c) => {
46
+ try {
47
+ return c.json(await abortStudioChatSession(requestWorkingDir(c), c.req.param('id')));
48
+ }
49
+ catch (err) {
50
+ return jsonOpencodeError(c, err);
51
+ }
52
+ });
53
+ chatSessions.post('/api/chat/sessions/:id/permission/:pid/respond', async (c) => {
54
+ const { response } = await c.req.json();
55
+ try {
56
+ return c.json(await respondSessionPermission(requestWorkingDir(c), c.req.param('id'), c.req.param('pid'), response));
57
+ }
58
+ catch (err) {
59
+ return jsonOpencodeError(c, err);
60
+ }
61
+ });
62
+ chatSessions.post('/api/chat/sessions/:id/share', async (c) => {
63
+ try {
64
+ return c.json(await shareStudioChatSession(requestWorkingDir(c), c.req.param('id')));
65
+ }
66
+ catch (err) {
67
+ return jsonOpencodeError(c, err);
68
+ }
69
+ });
70
+ chatSessions.post('/api/chat/sessions/:id/summarize', async (c) => {
71
+ const { providerID, modelID, auto } = await c.req.json();
72
+ try {
73
+ return c.json(await summarizeStudioChatSession(requestWorkingDir(c), c.req.param('id'), { providerID, modelID, auto }));
74
+ }
75
+ catch (err) {
76
+ return jsonOpencodeError(c, err);
77
+ }
78
+ });
79
+ chatSessions.post('/api/chat/sessions/:id/revert', async (c) => {
80
+ const { messageId, partId } = await c.req.json();
81
+ try {
82
+ return c.json(await revertStudioChatSession(requestWorkingDir(c), c.req.param('id'), { messageId, partId }));
83
+ }
84
+ catch (err) {
85
+ return jsonOpencodeError(c, err);
86
+ }
87
+ });
88
+ chatSessions.post('/api/chat/sessions/:id/unrevert', async (c) => {
89
+ try {
90
+ return c.json(await unrevertStudioChatSession(requestWorkingDir(c), c.req.param('id')));
91
+ }
92
+ catch (err) {
93
+ return jsonOpencodeError(c, err);
94
+ }
95
+ });
96
+ chatSessions.get('/api/chat/permissions', async (c) => {
97
+ try {
98
+ return c.json(await listPendingPermissions(requestWorkingDir(c)));
99
+ }
100
+ catch (err) {
101
+ return jsonOpencodeError(c, err);
102
+ }
103
+ });
104
+ export default chatSessions;
@@ -0,0 +1,15 @@
1
+ import { Hono } from 'hono';
2
+ import { jsonOpencodeError } from '../lib/opencode-errors.js';
3
+ import { createSSEResponse } from '../lib/sse.js';
4
+ import { buildStudioChatEventStream } from '../services/chat-event-stream-service.js';
5
+ import { requestWorkingDir } from './route-errors.js';
6
+ const chatStream = new Hono();
7
+ chatStream.get('/api/chat/events', async (c) => {
8
+ try {
9
+ return createSSEResponse(await buildStudioChatEventStream(requestWorkingDir(c), c.req.raw.signal));
10
+ }
11
+ catch (err) {
12
+ return jsonOpencodeError(c, err, { defaultStatus: 503 });
13
+ }
14
+ });
15
+ export default chatStream;