@turmind/halo 1.4.6 → 1.5.0-alpha

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 (76) hide show
  1. package/README.md +0 -1
  2. package/admin-out/404.html +1 -1
  3. package/admin-out/_next/static/chunks/160.c52d3d6cf7bc8286.js +1 -0
  4. package/admin-out/_next/static/chunks/220.e250bbbf65d24094.js +1 -0
  5. package/admin-out/_next/static/chunks/362.a0c58d3bf9b1300b.js +1 -0
  6. package/admin-out/_next/static/chunks/449.c4b881b9ebdc556e.js +1 -0
  7. package/admin-out/_next/static/chunks/613.fb09e55e0a61d3fe.js +1 -0
  8. package/admin-out/_next/static/chunks/73.05abce37d312e880.js +1 -0
  9. package/admin-out/_next/static/chunks/803-7128009ee0e1c10e.js +1 -0
  10. package/admin-out/_next/static/chunks/839.a2729fd33013d763.js +1 -0
  11. package/admin-out/_next/static/chunks/868.bbb91afd48c9eb58.js +1 -0
  12. package/admin-out/_next/static/chunks/892.a35118e09d8d8efd.js +1 -0
  13. package/admin-out/_next/static/chunks/989-7420ee650083df43.js +1 -0
  14. package/admin-out/_next/static/chunks/app/page-c33392db67853747.js +1 -0
  15. package/admin-out/_next/static/chunks/webpack-1a264efd965db7ab.js +1 -0
  16. package/admin-out/_next/static/css/7997e44c08e34b2b.css +3 -0
  17. package/admin-out/index.html +1 -1
  18. package/admin-out/index.txt +5 -5
  19. package/bundled-docs/design/agent.md +8 -7
  20. package/bundled-docs/design/architecture.md +9 -5
  21. package/bundled-docs/design/canvas-extensions.md +181 -0
  22. package/bundled-docs/design/cron.md +1 -1
  23. package/bundled-docs/design/feishu.md +3 -1
  24. package/bundled-docs/design/observability.md +2 -0
  25. package/bundled-docs/design/prompt-system.md +6 -10
  26. package/bundled-docs/design/relay.md +19 -15
  27. package/bundled-docs/design/session.md +15 -12
  28. package/bundled-docs/design/slack.md +2 -2
  29. package/bundled-docs/design/storage.md +4 -2
  30. package/bundled-docs/design/telegram.md +4 -2
  31. package/bundled-docs/design/wechat.md +7 -5
  32. package/bundled-docs/design/wecom.md +2 -2
  33. package/bundled-docs/design/ws.md +25 -18
  34. package/bundled-docs/dev/add-model-provider.md +21 -18
  35. package/bundled-docs/dev/api.md +25 -24
  36. package/bundled-docs/dev/env.md +2 -2
  37. package/bundled-docs/dev/tools.md +17 -82
  38. package/bundled-docs/guide/channels/wechat.md +1 -1
  39. package/bundled-docs/guide/cli.md +0 -1
  40. package/bundled-docs/guide/delegation-and-access.md +1 -2
  41. package/bundled-docs/guide/workspace.md +11 -0
  42. package/bundled-docs/requirements/chat.md +5 -4
  43. package/bundled-docs/requirements/command.md +1 -12
  44. package/bundled-docs/requirements/editor.md +9 -0
  45. package/bundled-docs/requirements/settings.md +9 -0
  46. package/dist/index.js +1898 -1037
  47. package/package.json +2 -2
  48. package/templates/agents/deep-executor/agent.yaml +0 -3
  49. package/templates/agents/default/AGENT.md +0 -17
  50. package/templates/agents/default/agent.yaml +0 -1
  51. package/templates/agents/executor/agent.yaml +1 -1
  52. package/templates/models/aws-bedrock-claude-invoke.yaml +31 -0
  53. package/templates/models/aws-bedrock-mantle.yaml +42 -7
  54. package/templates/models/aws-bedrock-openai.yaml +53 -17
  55. package/templates/prompts/root/DELEGATION.md +58 -0
  56. package/templates/skills/extension/SKILL.md +70 -0
  57. package/templates/skills/extension/templates/ext.sh +112 -0
  58. package/templates/skills/halo/SKILL.md +3 -0
  59. package/templates/skills/send-file/SKILL.md +1 -1
  60. package/admin-out/_next/static/chunks/220.1ed70c874686122f.js +0 -1
  61. package/admin-out/_next/static/chunks/23-092ac3901411582e.js +0 -1
  62. package/admin-out/_next/static/chunks/362.5970ef15ec3d7b15.js +0 -1
  63. package/admin-out/_next/static/chunks/449.a403f7ad18054e3f.js +0 -1
  64. package/admin-out/_next/static/chunks/613.f314fc12e05a566c.js +0 -1
  65. package/admin-out/_next/static/chunks/73.a4617a551a8e176c.js +0 -1
  66. package/admin-out/_next/static/chunks/767.16a0b33b6f5e7965.js +0 -1
  67. package/admin-out/_next/static/chunks/839.e381287756453ffe.js +0 -1
  68. package/admin-out/_next/static/chunks/868.cc7fd84c9f2fd711.js +0 -1
  69. package/admin-out/_next/static/chunks/892.92b2ed455683b49a.js +0 -1
  70. package/admin-out/_next/static/chunks/989-6a0ec35b8ae2cb39.js +0 -1
  71. package/admin-out/_next/static/chunks/app/page-bf9c0b21ae431c92.js +0 -1
  72. package/admin-out/_next/static/chunks/webpack-ca926228f8cba1ff.js +0 -1
  73. package/admin-out/_next/static/css/c0ea31cd787f7aa7.css +0 -3
  74. package/bundled-docs/design/goal-mode.md +0 -144
  75. /package/admin-out/_next/static/{qYSILjqBjfgTggO0T8E7T → Wn72NPclwda8BiGd0ChhY}/_buildManifest.js +0 -0
  76. /package/admin-out/_next/static/{qYSILjqBjfgTggO0T8E7T → Wn72NPclwda8BiGd0ChhY}/_ssgManifest.js +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@turmind/halo",
3
- "version": "1.4.6",
3
+ "version": "1.5.0-alpha",
4
4
  "description": "Halo — multi-agent collaborative workspace. CLI + TUI + HTTP/WS server in one package.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -18,7 +18,7 @@
18
18
  ],
19
19
  "dependencies": {
20
20
  "@aws-crypto/sha256-js": "^5.2.0",
21
- "@aws-sdk/client-bedrock-runtime": "^3.1030.0",
21
+ "@aws-sdk/client-bedrock-runtime": "^3.1142.0",
22
22
  "@aws-sdk/credential-provider-node": "^3.972.30",
23
23
  "@hono/node-server": "^1.19.17",
24
24
  "@larksuiteoapi/node-sdk": "^1.65.0",
@@ -13,9 +13,6 @@ context:
13
13
  compressAt: 0.8
14
14
  priority: 40
15
15
  tools:
16
- # `draft` matches this agent's draft-critique-revise persona (AGENT.md
17
- # "What deep means here") — deliberate, unlike executor which omits it.
18
- - draft
19
16
  - file_read
20
17
  - file_write
21
18
  - file_edit
@@ -23,23 +23,6 @@ useful. Don't reply off the cuff on questions that deserve care.
23
23
  one.) The agents you can reach are exactly the ones in the team roster
24
24
  in your system prompt.
25
25
 
26
- ## Briefing sub-agents
27
-
28
- A sub-agent starts from zero context — your brief is all it knows, and
29
- executors don't ask follow-up questions: under-specified corners become
30
- their best guess. A good brief names:
31
-
32
- - The deliverable and where it lives (paths, format), and what "done"
33
- looks like — build passes? tests green? which ones?
34
- - Boundaries, whenever they matter: files/dirs NOT to touch, whether
35
- committing / pushing / installing / running builds is allowed, and any
36
- in-flight work (yours or a parallel session's) it must not disturb.
37
- - When fanning out in parallel, partition the file set — two sessions
38
- editing the same files corrupt each other's work.
39
-
40
- One task per session. Bundling unrelated asks into one brief gets you a
41
- muddled summary and no way to retry one part alone.
42
-
43
26
  ## Talking to sub-agents
44
27
 
45
28
  When you start a sub-session, one sentence — "Asked X to do Y" — usually
@@ -19,7 +19,6 @@ context:
19
19
  compressAt: 0.8
20
20
  priority: 99
21
21
  tools:
22
- - draft
23
22
  - file_edit
24
23
  - file_write
25
24
  - file_read
@@ -2,7 +2,7 @@ name: Executor
2
2
  description: Workhorse for self-contained, well-specified tasks that would otherwise eat the parent's context — multi-file edits with a clear spec, batch shell chains, build/test loops, research across many web fetches. Takes a brief, executes end-to-end, returns a summary. Does not delegate further. Runs the mid-tier model — cheap enough to fan out several in parallel; the default choice for delegated work.
3
3
  model:
4
4
  provider: aws-bedrock-claude-invoke
5
- id: global.anthropic.claude-sonnet-5
5
+ id: global.anthropic.claude-sonnet-5-5
6
6
  endpoint: https://bedrock-runtime.us-east-1.amazonaws.com
7
7
  promptCaching: 1h
8
8
  thinking:
@@ -145,6 +145,37 @@ models:
145
145
  - { value: xhigh, label: XHigh }
146
146
  - { value: max, label: Max }
147
147
 
148
+ - id: global.anthropic.claude-sonnet-5-5
149
+ displayName: Claude Sonnet 5.5
150
+ maxOutputTokens: 64000
151
+ contextWindow: 1000000
152
+ capabilities:
153
+ image: true
154
+ video: false
155
+ audio: false
156
+ promptCaching:
157
+ default: 1h
158
+ ttlPresets:
159
+ - { value: 5m, label: 5min }
160
+ - { value: 1h, label: 1hour }
161
+ thinking:
162
+ # Sonnet 5.5 (Bedrock GA 2026-09-28; global profile only, no us.).
163
+ # Live-probed 2026-09-29 on invoke: invalid-value probe lists
164
+ # low / medium / high / xhigh / max; max_tokens ceiling 128000
165
+ # (maxOutputTokens kept at Sonnet 5's 64000); smoke call (xhigh,
166
+ # tool_use round trip) returned normally. Adaptive thinking
167
+ # always-on (type disabled / enabled both rejected); image input
168
+ # verified. Model card: 1M context.
169
+ mode: adaptive
170
+ defaultEnabled: true
171
+ default: high
172
+ effortPresets:
173
+ - { value: low, label: Low }
174
+ - { value: medium, label: Medium }
175
+ - { value: high, label: High }
176
+ - { value: xhigh, label: XHigh }
177
+ - { value: max, label: Max }
178
+
148
179
  - id: global.anthropic.claude-opus-4-6-v1
149
180
  displayName: Claude Opus 4.6
150
181
  maxOutputTokens: 128000
@@ -1,16 +1,17 @@
1
1
  id: aws-bedrock-mantle
2
- displayName: AWS Bedrock Mantle (OpenAI GPT-6 / 5.6)
3
- displayName_zh: AWS Bedrock Mantle(OpenAI GPT-6 / 5.6)
4
- description: OpenAI GPT-6 (Astra / Sol / Luna) and GPT-5.6 (Sol / Terra / Luna) on Amazon Bedrock via the bedrock-mantle endpoint (OpenAI Responses API only — not Converse/InvokeModel). Region-split — GPT-6 Sol / Luna are on us-east-1 only, Astra on us-west-2 only.
5
- description_zh: 通过 Bedrock Mantle 端点调用 OpenAI GPT-6(Astra / Sol / Luna)与 GPT-5.6(Sol / Terra / Luna)(仅 OpenAI Responses API,不支持 Converse/InvokeModel)。按区域分布:GPT-6 Sol / Luna 仅 us-east-1,Astra 仅 us-west-2。
2
+ displayName: AWS Bedrock Mantle (OpenAI GPT-6.1 / 6 / 5.6)
3
+ displayName_zh: AWS Bedrock Mantle(OpenAI GPT-6.1 / 6 / 5.6)
4
+ description: OpenAI GPT-6.1 Sol, GPT-6 (Astra / Sol / Luna) and GPT-5.6 (Sol / Terra / Luna) on Amazon Bedrock via the bedrock-mantle endpoint (OpenAI Responses API only — not Converse/InvokeModel). Region-split — GPT-6.1 Sol and GPT-6 Sol / Luna are on us-east-1 only, Astra on us-west-2 only.
5
+ description_zh: 通过 Bedrock Mantle 端点调用 OpenAI GPT-6.1 Sol、GPT-6(Astra / Sol / Luna)与 GPT-5.6(Sol / Terra / Luna)(仅 OpenAI Responses API,不支持 Converse/InvokeModel)。按区域分布:GPT-6.1 Sol 与 GPT-6 Sol / Luna 仅 us-east-1,Astra 仅 us-west-2。
6
6
  # Mantle is a distinct endpoint from bedrock-runtime; the OpenAI surface lives
7
- # under /openai/v1. Region split (live-probed 2026-09-24, 404 on the wrong
8
- # endpoint):
7
+ # under /openai/v1. Region split (live-probed 2026-09-24, gpt-6.1-sol
8
+ # 2026-09-30; 404 on the wrong endpoint):
9
9
  #
10
10
  # us-east-1 us-east-2 us-west-2
11
11
  # gpt-6-astra - - ✓
12
12
  # gpt-6-sol ✓ - -
13
13
  # gpt-6-luna ✓ - -
14
+ # gpt-6.1-sol ✓ - -
14
15
  # gpt-5.6-sol ✓ ✓ -
15
16
  # gpt-5.6-terra ✓ ✓ ✓
16
17
  # gpt-5.6-luna ✓ ✓ ✓
@@ -19,7 +20,7 @@ description_zh: 通过 Bedrock Mantle 端点调用 OpenAI GPT-6(Astra / Sol /
19
20
  # us-east-2 configs (5.6-era) keep working; Astra users pick us-west-2 in the
20
21
  # agent form. The endpoint is a per-agent pick.
21
22
  #
22
- # The same six models are also in aws-bedrock-openai.yaml (bedrock-runtime
23
+ # The same seven models are also in aws-bedrock-openai.yaml (bedrock-runtime
23
24
  # host, `global.openai.*` ids, no region split) — AWS's recommended endpoint
24
25
  # for new agents. Stay here only for Mantle-only features (server-side
25
26
  # web_search, background inference), which the web-search skill relies on.
@@ -138,6 +139,40 @@ models:
138
139
  - { value: medium, label: Medium }
139
140
  - { value: high, label: High }
140
141
 
142
+ # GPT-6.1 Sol (Bedrock GA 2026-09-29) — us-east-1 endpoint ONLY (404 on
143
+ # us-east-2 / us-west-2, live-probed 2026-09-30). reasoning.effort accepts
144
+ # low/medium/high/xhigh/max — `none` 400s, unlike GPT-6 Sol, so no "none"
145
+ # preset; text.verbosity low/medium/high; image input OK; 1,050,000 /
146
+ # 128,000 per the OpenAI model page.
147
+ - id: openai.gpt-6.1-sol
148
+ displayName: GPT-6.1 Sol
149
+ maxOutputTokens: 128000
150
+ contextWindow: 1050000
151
+ capabilities:
152
+ image: true
153
+ video: false
154
+ audio: false
155
+ promptCaching:
156
+ default: 5m
157
+ ttlPresets:
158
+ - { value: 5m, label: 5min }
159
+ thinking:
160
+ mode: adaptive
161
+ defaultEnabled: true
162
+ default: medium
163
+ effortPresets:
164
+ - { value: low, label: Low }
165
+ - { value: medium, label: Medium }
166
+ - { value: high, label: High }
167
+ - { value: xhigh, label: XHigh }
168
+ - { value: max, label: Max }
169
+ verbosity:
170
+ default: low
171
+ presets:
172
+ - { value: low, label: Low }
173
+ - { value: medium, label: Medium }
174
+ - { value: high, label: High }
175
+
141
176
  - id: openai.gpt-6-luna
142
177
  displayName: GPT-6 Luna
143
178
  maxOutputTokens: 128000
@@ -1,8 +1,8 @@
1
1
  id: aws-bedrock-openai
2
- displayName: AWS Bedrock OpenAI-compatible (GPT-6 / 5.6 · Grok 4.6 · Kimi K3)
3
- displayName_zh: AWS Bedrock OpenAI 兼容接口(GPT-6 / 5.6 · Grok 4.6 · Kimi K3)
4
- description: Frontier models on Amazon Bedrock via the bedrock-runtime endpoint's OpenAI Responses API (/openai/v1) — OpenAI GPT-6 (Astra / Sol / Luna) and GPT-5.6 (Sol / Terra / Luna), xAI Grok 4.6, Moonshot Kimi K3. Cross-Region inference profiles (global.*) only — every region serves every model, no region split. No server-side web_search (that is Mantle-only).
5
- description_zh: 通过 bedrock-runtime 端点的 OpenAI Responses API(/openai/v1)调用 Bedrock 上的前沿模型——OpenAI GPT-6(Astra / Sol / Luna)与 GPT-5.6(Sol / Terra / Luna)、xAI Grok 4.6、月之暗面 Kimi K3。仅支持跨区域推理 profile(global.*)——每个区域都能调所有模型,无区域分裂。不支持服务端 web_search(那是 Mantle 独有)。
2
+ displayName: AWS Bedrock OpenAI-compatible (GPT-6.1 / 6 / 5.6 · Grok 4.6 · Kimi K3)
3
+ displayName_zh: AWS Bedrock OpenAI 兼容接口(GPT-6.1 / 6 / 5.6 · Grok 4.6 · Kimi K3)
4
+ description: Frontier models on Amazon Bedrock via the bedrock-runtime endpoint's OpenAI Responses API (/openai/v1) — OpenAI GPT-6.1 Sol, GPT-6 (Astra / Sol / Luna) and GPT-5.6 (Sol / Terra / Luna), xAI Grok 4.6, Moonshot Kimi K3. Cross-Region inference profiles (global.*) only — every region serves every model, no region split. No server-side web_search (that is Mantle-only).
5
+ description_zh: 通过 bedrock-runtime 端点的 OpenAI Responses API(/openai/v1)调用 Bedrock 上的前沿模型——OpenAI GPT-6.1 Sol、GPT-6(Astra / Sol / Luna)与 GPT-5.6(Sol / Terra / Luna)、xAI Grok 4.6、月之暗面 Kimi K3。仅支持跨区域推理 profile(global.*)——每个区域都能调所有模型,无区域分裂。不支持服务端 web_search(那是 Mantle 独有)。
6
6
  # Why a second Bedrock/OpenAI provider next to aws-bedrock-mantle: this is the
7
7
  # bedrock-runtime host (https://bedrock-runtime.<region>.amazonaws.com/openai/v1),
8
8
  # which AWS now recommends for new applications; bedrock-mantle is the legacy
@@ -12,10 +12,10 @@ description_zh: 通过 bedrock-runtime 端点的 OpenAI Responses API(/openai/
12
12
  # the bare foundation-model id 400s with "on-demand throughput isn't
13
13
  # supported". Kimi K3 isn't on Mantle at all; Grok 4.6 is Mantle us-west-2
14
14
  # only — here everything is reachable from any region.
15
- # - live-probed 2026-09-24 with SigV4: all eight global.* ids below return
16
- # 200 in us-east-1 / us-west-2 / eu-west-1 / ap-northeast-1 alike, so the
17
- # endpoint is purely a latency / data-residency pick — unlike Mantle's
18
- # per-region GPT matrix.
15
+ # - live-probed 2026-09-24 with SigV4: all nine global.* ids below return
16
+ # 200 (gpt-6.1-sol probed 2026-09-30) in us-east-1 / us-west-2 /
17
+ # eu-west-1 / ap-northeast-1 alike, so the endpoint is purely a latency /
18
+ # data-residency pick — unlike Mantle's per-region GPT matrix.
19
19
  # - requests are always synchronous and there are NO server-side tools:
20
20
  # `tools:[{type:'web_search'}]` → 400 "web search is not supported for
21
21
  # this request" (verified). MantleAgent never sends those, but the
@@ -51,18 +51,20 @@ secrets:
51
51
  # which MantleAgent doesn't send) — the single 5m preset is a form hint only.
52
52
  # - thinking: reasoning.effort. Turning Thinking off in the form omits the
53
53
  # field (model default) — it does not send `none`. Good thing too: on this
54
- # host GPT-6 Astra 400s on `none` while every other GPT accepts it.
54
+ # host GPT-6 Astra and GPT-6.1 Sol 400 on `none` while every other GPT
55
+ # accepts it.
55
56
  # - Reasoning summaries are never surfaced (output[].reasoning.summary
56
57
  # empty) — same as GPT on Mantle; reasoning_tokens > 0 proves it ran.
57
58
  models:
58
- # OpenAI GPT-6 / GPT-5.6 — the same six models as aws-bedrock-mantle.yaml
59
- # (see that file for the per-model background: 1,050,000 / 128,000 for GPT-6,
60
- # 272,000 / 64,000 for 5.6, 2x input price past 272K). Live-probed
61
- # 2026-09-24 on this host with invalid-value requests: every one lists
62
- # reasoning.effort none/low/medium/high/xhigh/max in its own validation
63
- # error and accepts text.verbosity low/medium/high — identical surface, so
64
- # presets are copied verbatim. Pick these over the Mantle entries unless you
65
- # need Mantle-only features (server-side web_search, background inference).
59
+ # OpenAI GPT-6.1 / GPT-6 / GPT-5.6 — the same seven models as
60
+ # aws-bedrock-mantle.yaml (see that file for the per-model background:
61
+ # 1,050,000 / 128,000 for GPT-6, 272,000 / 64,000 for 5.6, 2x input price
62
+ # past 272K). Live-probed 2026-09-24 on this host with invalid-value
63
+ # requests: every one (except GPT-6.1 Sol, below) lists reasoning.effort
64
+ # none/low/medium/high/xhigh/max in its own validation error and accepts
65
+ # text.verbosity low/medium/high — identical surface, so presets are copied
66
+ # verbatim. Pick these over the Mantle entries unless you need Mantle-only
67
+ # features (server-side web_search, background inference).
66
68
  - id: global.openai.gpt-6-sol
67
69
  displayName: GPT-6 Sol
68
70
  maxOutputTokens: 128000
@@ -92,6 +94,40 @@ models:
92
94
  - { value: medium, label: Medium }
93
95
  - { value: high, label: High }
94
96
 
97
+ # GPT-6.1 Sol (Bedrock GA 2026-09-29). Live-probed 2026-09-30 on this host
98
+ # with SigV4: 200 from all four endpoint presets like the other global.* ids.
99
+ # reasoning.effort accepts low/medium/high/xhigh/max — `none` 400s, unlike
100
+ # GPT-6 Sol, so no "none" preset; text.verbosity low/medium/high; image input
101
+ # OK; 1,050,000 / 128,000 per the OpenAI model page.
102
+ - id: global.openai.gpt-6.1-sol
103
+ displayName: GPT-6.1 Sol
104
+ maxOutputTokens: 128000
105
+ contextWindow: 1050000
106
+ capabilities:
107
+ image: true
108
+ video: false
109
+ audio: false
110
+ promptCaching:
111
+ default: 5m
112
+ ttlPresets:
113
+ - { value: 5m, label: 5min }
114
+ thinking:
115
+ mode: adaptive
116
+ defaultEnabled: true
117
+ default: medium
118
+ effortPresets:
119
+ - { value: low, label: Low }
120
+ - { value: medium, label: Medium }
121
+ - { value: high, label: High }
122
+ - { value: xhigh, label: XHigh }
123
+ - { value: max, label: Max }
124
+ verbosity:
125
+ default: low
126
+ presets:
127
+ - { value: low, label: Low }
128
+ - { value: medium, label: Medium }
129
+ - { value: high, label: High }
130
+
95
131
  - id: global.openai.gpt-6-astra
96
132
  displayName: GPT-6 Astra
97
133
  maxOutputTokens: 128000
@@ -0,0 +1,58 @@
1
+ ## Delegation
2
+
3
+ This applies when your tools include `start_session`. The agents you can hand
4
+ work to are listed under "Your Team". Routing and size thresholds for this
5
+ workspace are in its INSTRUCTIONS; this section is what holds in any workspace.
6
+
7
+ A sub-session runs in parallel with your conversation and reports back when it
8
+ finishes. While you run tools, the user's messages can only be answered between
9
+ calls. Both choices cost something: a sub-session you didn't need costs a
10
+ brief to write and a report to read; work you kept costs the user's ability to
11
+ talk to you while you're busy, and fills your context with intermediate output.
12
+
13
+ ### What tends to fit a sub-session
14
+
15
+ - The piece stands alone: it can be stated as a task with a checkable result.
16
+ - It can run alongside other pieces, or alongside your conversation with the user.
17
+ - Its working output (grep hits, file dumps, build logs) is large compared with
18
+ the answer you need back.
19
+ - It needs an edit → build/test → fix loop that you don't need to watch.
20
+ - A roster agent is set up for exactly this kind of work.
21
+
22
+ ### What tends to fit doing it yourself
23
+
24
+ - It leans on this conversation: decisions the user made, options they ruled
25
+ out, constraints they mentioned in passing — and restating those in a brief
26
+ would take about as long as the work.
27
+ - It is small and its cause is already known.
28
+ - The user is waiting on an answer, or wants to steer step by step.
29
+ - Your next step depends on the result and there is nothing else to do meanwhile.
30
+
31
+ ### Three checks before `start_session`
32
+
33
+ 1. Could someone with no access to this chat do the task from the brief alone?
34
+ If the missing part can't be written in a few lines, do it yourself or ask
35
+ the user.
36
+ 2. Does it need more than one edit-and-verify round, or more than a handful of
37
+ tool calls? If not, doing it yourself is usually cheaper.
38
+ 3. Will the user want to talk to you in the meantime? If so, hand off the long
39
+ part and stay reachable.
40
+
41
+ ### What a brief contains
42
+
43
+ The worker starts with only the brief and does not ask follow-ups. What the
44
+ brief leaves out, it explores or guesses.
45
+
46
+ - **Goal and deliverable**: what to produce, where it lives, what "done" means.
47
+ - **Decisions already made in this conversation**: the user's choices, rejected
48
+ options, and constraints. Quote the user's wording when it matters.
49
+ - **What you already know**: paths, line numbers, snippets, the pattern to copy.
50
+ Findings, not pointers.
51
+ - **Boundaries**: files not to touch; whether commit / push / build / deploy is
52
+ allowed; in-flight work it must not disturb.
53
+ - **Verification**: the exact commands, scoped to what changed.
54
+ - **What it need not read or redo.**
55
+
56
+ One task per session. When fanning out, partition by file so parallel sessions
57
+ don't edit the same ones. For a follow-up or a fix from the same worker,
58
+ `query_session` continues it with its context intact.
@@ -0,0 +1,70 @@
1
+ ---
2
+ name: extension
3
+ description: Install / list / remove admin preview extensions (`~/.halo/global/extensions/<id>/`) — static viewers the admin editor loads for file types it has no built-in preview for (e.g. `.glb`). `/extension install <id|zip|url>` pulls the latest `<id>-v*` release from turmind/halo-hub by default. Activate when the user wants to preview a file type the editor can't open, or asks to install / update / remove an extension.
4
+ command: /extension
5
+ requiresAccess: full
6
+ verbs:
7
+ - { name: install, desc: "Install or upgrade an extension from halo-hub (by id), a local zip path, or a zip URL" }
8
+ - { name: list, desc: List installed extensions with version and status }
9
+ - { name: remove, desc: Remove an installed extension by id }
10
+ ---
11
+
12
+ # extension
13
+
14
+ The requested action is **`$1`**; the rest of the line is the payload. With
15
+ natural language ("install the glb viewer"), infer both.
16
+
17
+ Preview extensions are static bundles the admin editor loads in a sandboxed
18
+ iframe to preview file types it has no built-in viewer for (e.g. `.glb`).
19
+ Installing = putting a validated directory under `~/.halo/global/extensions/<id>/`;
20
+ the server watches that directory and pushes the change to every open admin —
21
+ **no restart, no page reload**.
22
+
23
+ The helper is `templates/ext.sh` in this skill's directory — the workspace copy
24
+ wins if it exists, else the global one:
25
+
26
+ - `<workspace>/.halo/skills/extension/templates/ext.sh`
27
+ - `~/.halo/global/skills/extension/templates/ext.sh`
28
+
29
+ Pick EXT = the first that exists, then:
30
+
31
+ ## install
32
+
33
+ `bash EXT install <source>` where `<source>` is one of:
34
+ - an extension id (e.g. `glb`) → downloads the newest `glb-v*` release asset
35
+ (`glb-<version>.zip`) from https://github.com/turmind/halo-hub/releases;
36
+ - a local `.zip` path;
37
+ - an `https://…zip` URL.
38
+
39
+ The script unpacks to a temp dir, pre-checks `halo-extension.json` (id matches,
40
+ entry exists), then atomically replaces `~/.halo/global/extensions/<id>/`.
41
+ Reinstalling the same id upgrades (or downgrades) it. Relay the script's last
42
+ line to the user (`installed glb 1.0.0` / the error). Never edit files inside an
43
+ installed extension — reinstall instead.
44
+
45
+ If the download fails with "no release for <id>" the GitHub API may be
46
+ rate-limited (60 unauthenticated requests/hour) or the id may not exist: point
47
+ the user at https://github.com/turmind/halo-hub/releases to download the zip by
48
+ hand, then `install <path>`. Setting `GITHUB_TOKEN` in the environment lifts
49
+ the limit.
50
+
51
+ ## list
52
+
53
+ `bash EXT list` → one line per extension: `<id>\t<version>\t<name>` or
54
+ `<id>\tERROR\t<reason>` for directories the server would reject. Relay as a
55
+ short `-` list; `(none)` when empty.
56
+
57
+ ## remove
58
+
59
+ `bash EXT remove <id>` → deletes `~/.halo/global/extensions/<id>/`. Confirm with
60
+ the user first if they didn't name the id explicitly.
61
+
62
+ ## Notes
63
+
64
+ - Extensions are global (all workspaces on this server). Say so if the user
65
+ asks "install it for this project".
66
+ - To find what's available, point the user at https://github.com/turmind/halo-hub
67
+ (the `extensions/` directory); do not scrape it.
68
+ - The script only pre-checks the required fields; the server's scanner is the
69
+ authority. A package it rejects shows up in `list` (and the admin) as
70
+ `ERROR` with the reason.
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env bash
2
+ # Install / list / remove admin preview extensions under ~/.halo/global/extensions/.
3
+ # The directory IS the install: the server watches the root and pushes changes
4
+ # to every open admin, so nothing here talks to the server. Validation
5
+ # authority is the server's scanner; this script only pre-checks the required
6
+ # fields so a broken package never replaces a working install.
7
+ #
8
+ # ext.sh install <id | path/to.zip | https://…zip>
9
+ # ext.sh list
10
+ # ext.sh remove <id>
11
+ #
12
+ # Needs curl + unzip + node. No `gh`: releases are read through the public API
13
+ # (GITHUB_TOKEN, if set, is sent to lift the 60 req/h anonymous limit).
14
+ set -euo pipefail
15
+
16
+ ROOT="$HOME/.halo/global/extensions"
17
+ REPO="turmind/halo-hub"
18
+ ID_RE='^[a-z0-9][a-z0-9_-]{0,63}$' # keep in sync with server extensions/registry.ts ID_RE
19
+
20
+ cmd=${1:?usage: ext.sh install <id|zip|url> | list | remove <id>}
21
+ shift || true
22
+
23
+ # Print `<id> <version> <name>` for the extension dir $1, or fail with the
24
+ # reason. $2 (optional) = the id the directory / request claims.
25
+ precheck() {
26
+ node -e '
27
+ const fs = require("fs"), dir = process.argv[1], expect = process.argv[2] || ""
28
+ const fail = (msg) => { console.log(msg); process.exit(1) }
29
+ let m
30
+ try { m = JSON.parse(fs.readFileSync(dir + "/halo-extension.json", "utf8")) } catch (e) { fail("halo-extension.json missing or invalid: " + e.message) }
31
+ for (const k of ["id", "name", "version", "extensions", "entry"]) if (!m[k]) fail("missing " + k)
32
+ if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(m.id)) fail("bad id: " + m.id)
33
+ if (expect && m.id !== expect) fail("manifest id \"" + m.id + "\" != \"" + expect + "\"")
34
+ let isFile = false
35
+ try { isFile = fs.statSync(dir + "/" + m.entry).isFile() } catch {}
36
+ if (!isFile) fail("entry not found: " + m.entry)
37
+ console.log(m.id + " " + m.version + " " + m.name)
38
+ ' "$1" "${2:-}"
39
+ }
40
+
41
+ # Echo the path of a local zip for source $1 (path, https URL, or hub id).
42
+ resolve_source() {
43
+ local src=$1 url
44
+ if [[ -f $src ]]; then echo "$src"; return; fi
45
+ if [[ $src =~ ^https?:// ]]; then curl -fsSL "$src" -o "$TMP/pkg.zip"; echo "$TMP/pkg.zip"; return; fi
46
+ [[ $src =~ $ID_RE ]] || { echo "invalid id or missing file: $src" >&2; exit 1; }
47
+ # Newest non-draft, non-prerelease release tagged "<id>-v*" (API is newest-first).
48
+ url=$(curl -fsSL ${GITHUB_TOKEN:+-H "Authorization: Bearer $GITHUB_TOKEN"} \
49
+ "https://api.github.com/repos/$REPO/releases?per_page=100" \
50
+ | node -e '
51
+ let s = ""; process.stdin.on("data", (d) => s += d).on("end", () => {
52
+ const id = process.argv[1]
53
+ const r = JSON.parse(s).find((r) => r.tag_name.startsWith(id + "-v") && !r.draft && !r.prerelease)
54
+ const a = r && r.assets.find((a) => a.name.endsWith(".zip"))
55
+ if (!a) process.exit(2)
56
+ console.log(a.browser_download_url)
57
+ })' "$src") \
58
+ || { echo "no release for $src in $REPO — download the zip from https://github.com/$REPO/releases and run: install <path>" >&2; exit 1; }
59
+ curl -fsSL "$url" -o "$TMP/pkg.zip"
60
+ echo "$TMP/pkg.zip"
61
+ }
62
+
63
+ case $cmd in
64
+ install)
65
+ src=${1:?usage: ext.sh install <id|zip|url>}
66
+ TMP=$(mktemp -d); trap 'rm -rf "$TMP"' EXIT
67
+ zip=$(resolve_source "$src")
68
+ # Same refusals as the server's zip installer: no `..`, no absolute paths,
69
+ # no symlinks (zipinfo mode column starts with `l`).
70
+ if unzip -Z1 "$zip" | grep -Eq '(^|/)\.\.(/|$)|^/'; then echo "zip contains ../ or absolute entries" >&2; exit 1; fi
71
+ if unzip -Z "$zip" | grep -q '^l'; then echo "zip contains symlinks" >&2; exit 1; fi
72
+ mkdir -p "$TMP/x"
73
+ unzip -q "$zip" -d "$TMP/x"
74
+ # `zip -r glb.zip glb/` wraps everything in one directory — descend into it.
75
+ dir="$TMP/x"
76
+ if [[ ! -f $dir/halo-extension.json ]]; then
77
+ only=$(find "$dir" -mindepth 1 -maxdepth 1 | head -2)
78
+ if [[ $(printf '%s\n' "$only" | wc -l) -eq 1 && -f $only/halo-extension.json ]]; then dir=$only; fi
79
+ fi
80
+ expect=""
81
+ if [[ $src =~ $ID_RE && ! -f $src ]]; then expect=$src; fi
82
+ if ! out=$(precheck "$dir" "$expect"); then echo "invalid extension: $out" >&2; exit 1; fi
83
+ read -r id ver _ <<<"$out"
84
+ mkdir -p "$ROOT"
85
+ stage="$ROOT/.tmp-$id-$RANDOM$RANDOM"
86
+ mv "$dir" "$stage"
87
+ old=""
88
+ if [[ -d $ROOT/$id ]]; then old="$ROOT/.old-$id-$RANDOM$RANDOM"; mv "$ROOT/$id" "$old"; fi
89
+ mv "$stage" "$ROOT/$id" || { [[ -n $old ]] && mv "$old" "$ROOT/$id"; rm -rf "$stage"; exit 1; }
90
+ if [[ -n $old ]]; then rm -rf "$old"; fi
91
+ echo "installed $id $ver" ;;
92
+ list)
93
+ shopt -s nullglob
94
+ n=0
95
+ for d in "$ROOT"/*/; do
96
+ d=${d%/}; id=$(basename "$d"); n=$((n + 1))
97
+ if out=$(precheck "$d" "$id"); then
98
+ read -r _ ver name <<<"$out"; printf '%s\t%s\t%s\n' "$id" "$ver" "$name"
99
+ else
100
+ printf '%s\tERROR\t%s\n' "$id" "$(printf '%s' "$out" | head -1)"
101
+ fi
102
+ done
103
+ if [[ $n -eq 0 ]]; then echo "(none)"; fi ;;
104
+ remove)
105
+ id=${1:?usage: ext.sh remove <id>}
106
+ [[ $id =~ $ID_RE ]] || { echo "invalid id: $id" >&2; exit 1; }
107
+ [[ -d $ROOT/$id ]] || { echo "not installed: $id" >&2; exit 1; }
108
+ rm -rf "$ROOT/$id"
109
+ echo "removed $id" ;;
110
+ *)
111
+ echo "unknown command: $cmd (install | list | remove)" >&2; exit 1 ;;
112
+ esac
@@ -13,6 +13,7 @@ You are running inside **Halo**, a multi-agent collaboration workspace.
13
13
  | Location | Scope | Purpose |
14
14
  |----------|-------|---------|
15
15
  | `~/.halo/global/` | Global (all projects) | User profile, global instructions, global agents & skills, system prompts, bundled docs |
16
+ | `~/.halo/global/extensions/<id>/` | Global (all projects) | Installed admin preview extensions (static viewers for file types the editor can't open, e.g. `.glb`); managed with the `extension` skill (`/extension install|list|remove`) |
16
17
  | `<workspace>/.halo/` | Per-project | Project instructions, knowledge index, project agents & skills |
17
18
 
18
19
  `.halo/` is excluded from `grep` and `glob`. Use `file_read` with the exact path to access files inside.
@@ -97,3 +98,5 @@ Questions outside these rows aren't in the bundled docs (backend internals, REST
97
98
  ## Skills
98
99
 
99
100
  Skills are referenced by id from an agent's `agent.yaml`. The system prompt shows each skill's name + description; the full body is loaded on demand via `activate_skill`.
101
+
102
+ Admin preview extensions (`.glb` and other file types the editor has no built-in viewer for) are not skills — they are static bundles under `~/.halo/global/extensions/`; the `extension` skill installs / lists / removes them.
@@ -13,7 +13,7 @@ Multiple `MEDIA:` lines in one reply = multiple attachments.
13
13
 
14
14
  - Marker must be alone on a line. No leading spaces, no inline text.
15
15
  - Path must be absolute and the file must exist. Verify with `file_list` if unsure.
16
- - The file must live under the current workspace directory or the OS temp dir (`/tmp` on Linux/macOS). Any other path — including another workspace on the same server — is silently dropped: no attachment arrives and you get no error. Copy the file into the workspace (e.g. `.halo/tmp/`) or `/tmp` first, then reference the copy.
16
+ - Where the file may live depends on the channel account's access level. A full-access account may send any readable absolute path. A workspace / readonly account is limited to the current workspace directory or the OS temp dir (`/tmp` on Linux/macOS); any other path — including another workspace on the same server — is not sent, and the user sees a "⚠️ 文件上传失败 / delivery failed" message naming the file. If you are not full-access, copy the file into the workspace (e.g. `.halo/tmp/`) or `/tmp` first, then reference the copy.
17
17
  - Write normal text around `MEDIA:` lines for context.
18
18
 
19
19
  ## Example
@@ -1 +0,0 @@
1
- "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[220],{10912:(e,t,n)=>{function r(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"font-family:sans-serif;padding:40px;max-width:800px;margin:0 auto",r=window.open("","_blank");r&&(r.document.write("<!DOCTYPE html><html><head><title>".concat(e.replace(/[&<>"']/g,e=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[e]),"</title><style>body{").concat(n,"}img{max-width:100%}</style></head><body>").concat(t,"</body></html>")),r.document.close(),r.onload=()=>{r.print()})}n.d(t,{H:()=>r})},37050:(e,t,n)=>{n.d(t,{I:()=>o,n:()=>s});var r=n(41440),l=n(48434),a=n(90302),i=n(17119);function s(e){let{name:t,downloadUrl:n,onOpenAsText:s,extraToolbar:c,loading:d,error:u,children:x}=e,h=(0,i.kj)();return(0,r.jsxs)("div",{className:"flex h-full flex-col bg-[var(--background)]",children:[(0,r.jsxs)("div",{className:"flex h-8 shrink-0 items-center gap-2 border-b border-[var(--border)] bg-[var(--card)] px-3",children:[(0,r.jsx)("span",{className:"truncate text-xs text-[var(--muted-foreground)]",children:t}),(0,r.jsx)("div",{className:"flex-1"}),c,s&&(0,r.jsxs)(o,{onClick:s,title:h("editor.openAsText"),children:[(0,r.jsx)(l.A,{className:"h-3 w-3"}),(0,r.jsx)("span",{children:h("editor.openAsText")})]}),(0,r.jsxs)("a",{href:n,download:!0,className:"flex items-center gap-1 rounded px-2 py-1 text-[10px] font-medium text-[var(--muted-foreground)] transition-colors hover:bg-[var(--secondary)] hover:text-[var(--foreground)]",title:h("editor.download"),children:[(0,r.jsx)(a.A,{className:"h-3 w-3"}),(0,r.jsx)("span",{children:h("editor.download")})]})]}),(0,r.jsx)("div",{className:"relative min-h-0 flex-1",children:u?(0,r.jsx)("div",{className:"flex h-full items-center justify-center text-sm text-red-400",children:u}):(0,r.jsxs)(r.Fragment,{children:[x,d&&(0,r.jsx)("div",{className:"absolute inset-0 flex items-center justify-center bg-[var(--background)]/80 text-sm text-[var(--muted-foreground)]",children:"Loading preview..."})]})})]})}function o(e){let{onClick:t,title:n,children:l}=e;return(0,r.jsx)("button",{onClick:t,title:n,className:"flex items-center gap-1 rounded px-2 py-1 text-[10px] font-medium text-[var(--muted-foreground)] transition-colors hover:bg-[var(--secondary)] hover:text-[var(--foreground)]",children:l})}},39250:(e,t,n)=>{n.d(t,{A:()=>r});let r=(0,n(79020).A)("Printer",[["path",{d:"M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2",key:"143wyd"}],["path",{d:"M6 9V3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v6",key:"1itne7"}],["rect",{x:"6",y:"14",width:"12",height:"8",rx:"1",key:"1ue0tg"}]])},48434:(e,t,n)=>{n.d(t,{A:()=>r});let r=(0,n(79020).A)("ArrowRightLeft",[["path",{d:"m16 3 4 4-4 4",key:"1x1c3m"}],["path",{d:"M20 7H4",key:"zbl0bi"}],["path",{d:"m8 21-4-4 4-4",key:"h9nckh"}],["path",{d:"M4 17h16",key:"g4d7ey"}]])},62839:(e,t,n)=>{n.r(t),n.d(t,{MediaPreview:()=>m});var r=n(41440),l=n(54604),a=n(79020);let i=(0,a.A)("ZoomOut",[["circle",{cx:"11",cy:"11",r:"8",key:"4ej97u"}],["line",{x1:"21",x2:"16.65",y1:"21",y2:"16.65",key:"13gj7c"}],["line",{x1:"8",x2:"14",y1:"11",y2:"11",key:"durymu"}]]),s=(0,a.A)("ZoomIn",[["circle",{cx:"11",cy:"11",r:"8",key:"4ej97u"}],["line",{x1:"21",x2:"16.65",y1:"21",y2:"16.65",key:"13gj7c"}],["line",{x1:"11",x2:"11",y1:"8",y2:"14",key:"1vmskp"}],["line",{x1:"8",x2:"14",y1:"11",y2:"11",key:"durymu"}]]);var o=n(23715),c=n(39250),d=n(37050),u=n(10912),x=n(27173),h=n(17119);function m(e){let{name:t,viewUrl:n,downloadUrl:a,onOpenAsText:m}=e,f=(0,h.kj)(),g=function(e){var t,n;let r=null!=(n=null==(t=e.split(".").pop())?void 0:t.toLowerCase())?n:"";return x.dj.includes(r)?"image":x.Tv.includes(r)?"video":x._H.includes(r)?"audio":null}(t),p=function(){let e=(0,l.useRef)(null),[t,n]=(0,l.useState)(1),[r,a]=(0,l.useState)({x:0,y:0}),i=(0,l.useRef)(null),[s,o]=(0,l.useState)(!1),c=(0,l.useCallback)(()=>{n(1),a({x:0,y:0})},[]),d=(0,l.useCallback)((e,t,r)=>{n(n=>{let l=Math.min(20,Math.max(.1,n*r)),i=l/n;return a(n=>({x:e-i*(e-n.x),y:t-i*(t-n.y)})),l})},[]),u=(0,l.useCallback)(t=>{let n=e.current;n&&d(n.clientWidth/2,n.clientHeight/2,t)},[d]),x=(0,l.useCallback)(t=>{var n;t.preventDefault();let r=null==(n=e.current)?void 0:n.getBoundingClientRect();r&&d(t.clientX-r.left,t.clientY-r.top,t.deltaY<0?1.15:.8695652173913044)},[d]),h=(0,l.useCallback)(e=>{0===e.button&&(e.preventDefault(),e.target.setPointerCapture(e.pointerId),i.current={startX:e.clientX,startY:e.clientY,origX:r.x,origY:r.y},o(!0))},[r]),m=(0,l.useCallback)(e=>{i.current&&a({x:i.current.origX+(e.clientX-i.current.startX),y:i.current.origY+(e.clientY-i.current.startY)})},[]),f=(0,l.useCallback)(()=>{i.current=null,o(!1)},[]);return(0,l.useEffect)(()=>{let t=e.current;if(!t)return;let n=e=>e.preventDefault();return t.addEventListener("wheel",n,{passive:!1}),()=>t.removeEventListener("wheel",n)},[]),{containerRef:e,scale:t,translate:r,dragging:s,reset:c,zoomCenter:u,onWheel:x,onPointerDown:h,onPointerMove:m,onPointerUp:f}}(),v="image"===g?(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(d.I,{onClick:()=>p.zoomCenter(.8695652173913044),title:f("editor.zoomOut"),children:(0,r.jsx)(i,{className:"h-3 w-3"})}),(0,r.jsxs)("span",{className:"min-w-[3ch] text-center text-[10px] text-[var(--muted-foreground)]",children:[Math.round(100*p.scale),"%"]}),(0,r.jsx)(d.I,{onClick:()=>p.zoomCenter(1.15),title:f("editor.zoomIn"),children:(0,r.jsx)(s,{className:"h-3 w-3"})}),(0,r.jsx)(d.I,{onClick:p.reset,title:f("editor.resetZoom"),children:(0,r.jsx)(o.A,{className:"h-3 w-3"})}),(0,r.jsxs)(d.I,{onClick:()=>(0,u.H)(t,'<img src="'.concat(n,'" />'),"margin:0;display:flex;justify-content:center;align-items:center;min-height:100vh;background:#fff"),title:f("editor.print"),children:[(0,r.jsx)(c.A,{className:"h-3 w-3"}),(0,r.jsx)("span",{children:f("editor.print")})]})]}):null;return(0,r.jsxs)(d.n,{name:t,downloadUrl:a,onOpenAsText:m,extraToolbar:v,children:["image"===g&&(0,r.jsx)("div",{ref:p.containerRef,className:"h-full w-full overflow-hidden bg-[#1a1a1a]",style:{cursor:p.dragging?"grabbing":"grab"},onWheel:p.onWheel,onPointerDown:p.onPointerDown,onPointerMove:p.onPointerMove,onPointerUp:p.onPointerUp,onPointerCancel:p.onPointerUp,children:(0,r.jsx)("img",{src:n,alt:t,draggable:!1,style:{transformOrigin:"0 0",transform:"translate(".concat(p.translate.x,"px, ").concat(p.translate.y,"px) scale(").concat(p.scale,")")}})}),"video"===g&&(0,r.jsx)("div",{className:"flex h-full items-center justify-center bg-black p-4",children:(0,r.jsx)("video",{src:n,controls:!0,className:"max-h-full max-w-full",children:(0,r.jsx)("track",{kind:"captions"})})}),"audio"===g&&(0,r.jsx)("div",{className:"flex h-full flex-col items-center justify-center gap-4 bg-[var(--background)]",children:(0,r.jsx)("audio",{src:n,controls:!0,className:"w-full max-w-md"})})]})}}}]);