@turmind/halo 0.1.9 → 0.2.1-bc7cf2b

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 (66) hide show
  1. package/README.md +1 -5
  2. package/admin-out/404.html +1 -1
  3. package/admin-out/_next/static/chunks/212-170855bed19f67f3.js +1 -0
  4. package/admin-out/_next/static/chunks/{503-7c6d9935df6e0cea.js → 307-d7a99f9a4a436ec3.js} +1 -1
  5. package/admin-out/_next/static/chunks/525.2c10a4fd329d0d24.js +1 -0
  6. package/admin-out/_next/static/chunks/app/layout-c2fa8035998e48d5.js +1 -0
  7. package/admin-out/_next/static/chunks/app/page-31317ef1fb0ba31f.js +1 -0
  8. package/admin-out/_next/static/chunks/{webpack-8bd7ef88a6527feb.js → webpack-47520cdc55be63e7.js} +1 -1
  9. package/admin-out/_next/static/css/66df341f99220f5c.css +3 -0
  10. package/admin-out/index.html +1 -1
  11. package/admin-out/index.txt +21 -20
  12. package/bundled-docs/design/agent.md +1 -1
  13. package/bundled-docs/design/evolution.md +11 -2
  14. package/bundled-docs/design/halo-city.md +31 -0
  15. package/bundled-docs/design/prompt-system.md +3 -3
  16. package/bundled-docs/design/session.md +26 -1
  17. package/bundled-docs/design/storage.md +13 -0
  18. package/bundled-docs/dev/acp-adapter.md +31 -4
  19. package/bundled-docs/dev/api.md +6 -4
  20. package/bundled-docs/dev/desktop-packaging.md +98 -8
  21. package/bundled-docs/dev/env.md +4 -2
  22. package/bundled-docs/dev/previews.md +3 -0
  23. package/bundled-docs/dev/tools.md +3 -1
  24. package/bundled-docs/guide/cli.md +40 -16
  25. package/bundled-docs/guide/getting-started.md +3 -2
  26. package/bundled-docs/requirements/editor.md +1 -1
  27. package/bundled-docs/requirements/explorer.md +11 -0
  28. package/dist/index.js +14102 -13249
  29. package/package.json +1 -2
  30. package/templates/INSTRUCTIONS.md +0 -4
  31. package/templates/agents/__apply_agent__/AGENT.md +41 -19
  32. package/templates/agents/__evo_agent__/AGENT.md +73 -45
  33. package/templates/agents/__evo_agent__/agent.yaml +9 -6
  34. package/templates/agents/__score__/AGENT.md +65 -33
  35. package/templates/agents/deep-executor/AGENT.md +23 -6
  36. package/templates/agents/deep-executor/agent.yaml +4 -1
  37. package/templates/agents/default/AGENT.md +32 -8
  38. package/templates/agents/executor/AGENT.md +19 -1
  39. package/templates/agents/executor/agent.yaml +6 -3
  40. package/templates/models/aws-bedrock-claude-invoke.yaml +53 -0
  41. package/templates/prompts/all/WORKSPACE_CONVENTIONS.md +5 -0
  42. package/templates/prompts/bootstrap/BOOTSTRAP.md +4 -0
  43. package/templates/prompts/root/WORKSPACE_MEMORY.md +2 -0
  44. package/templates/skills/acp/SKILL.md +8 -1
  45. package/templates/skills/acp/templates/SKILL.md.tmpl +2 -1
  46. package/templates/skills/acp/templates/ask.py +2 -1
  47. package/templates/skills/acp/templates/config.yaml.tmpl +5 -0
  48. package/admin-out/_next/static/chunks/298-d6b224b569da4de0.js +0 -1
  49. package/admin-out/_next/static/chunks/661.a4fe93f3f2a5e871.js +0 -1
  50. package/admin-out/_next/static/chunks/app/layout-58f773336e6fce61.js +0 -1
  51. package/admin-out/_next/static/chunks/app/page-cc8043335282d745.js +0 -1
  52. package/admin-out/_next/static/css/8fce4e1a54dac12a.css +0 -3
  53. package/bundled-docs/plans/cron-tasks.md +0 -362
  54. package/bundled-docs/plans/design-suggestions.md +0 -203
  55. package/bundled-docs/plans/issue-session-deep-nesting.md +0 -208
  56. package/bundled-docs/plans/launch-plan.md +0 -111
  57. package/bundled-docs/plans/md-and-agent-refactor.md +0 -197
  58. package/bundled-docs/plans/remote-mac-worker.md +0 -196
  59. package/bundled-docs/plans/roadmap.md +0 -40
  60. package/bundled-docs/plans/self-evolution.md +0 -1379
  61. package/bundled-docs/plans/todo.md +0 -32
  62. package/bundled-docs/plans/weixin-channel.md +0 -255
  63. package/bundled-docs/test/docs-seeding.md +0 -101
  64. package/bundled-docs/test/session.md +0 -374
  65. /package/admin-out/_next/static/{IBP_mwBDf-cPwZSZZGpi8 → iSrc6429pyTB0lK-EE5oH}/_buildManifest.js +0 -0
  66. /package/admin-out/_next/static/{IBP_mwBDf-cPwZSZZGpi8 → iSrc6429pyTB0lK-EE5oH}/_ssgManifest.js +0 -0
@@ -4,17 +4,41 @@ You are the Default agent — Halo's general-purpose orchestrator. You are the
4
4
  agent the user lands on by default in any workspace, and you're also a valid
5
5
  target when other agents want to delegate to a generalist.
6
6
 
7
+ Hold your answers to a high standard — accurate, well-checked, and directly
8
+ useful. Don't reply off the cuff on questions that deserve care.
9
+
7
10
  ## Multi-Layer Delegation
8
11
 
9
- - Sub-agents you spawn already get `query_session` so they can reply to you.
10
- - If a sub-agent needs to delegate further (grandchild sessions), its
11
- `agent.yaml` `tools` field must list the session-management tools:
12
- `start_session`, `session_list`, `query_session`, `interrupt_session`,
13
- `stop_session`, `get_session_output`, `query_agent`.
12
+ - Sub-agents you spawn report back automatically when they finish — the
13
+ wrap-up reply arrives as a message from the child session (long reports
14
+ are truncated; `get_session_output` fetches the full text).
15
+ - A finished child session stays usable: `query_session` continues its
16
+ conversation with full context — prefer that over spawning a fresh
17
+ session when you need a follow-up or a fix from the same worker.
14
18
  - Reports propagate up the chain automatically: grandchild → parent → you.
15
- - Who an agent may delegate to is its `team` whitelist in `agent.yaml` (unset =
16
- every agent). The agents you can actually reach are the ones listed in "Know
17
- Your Team" below — that roster already reflects your whitelist.
19
+ - Whether an agent can delegate further is decided by the `team` whitelist
20
+ in its `agent.yaml`: a non-empty list grants the whole session-tool
21
+ bundle and scopes who's reachable; absent/empty means it cannot
22
+ delegate at all. (The `agent` skill has the full spec when you create
23
+ one.) The agents you can reach are exactly the ones in the team roster
24
+ in your system prompt.
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.
18
42
 
19
43
  ## Talking to sub-agents
20
44
 
@@ -1,6 +1,6 @@
1
1
  # Executor
2
2
 
3
- You are an Executor — a single-task worker. The default agent (your parent)
3
+ You are an Executor — a single-task worker. Your parent agent
4
4
  delegates self-contained work to you to keep its main context clean. You
5
5
  finish the task, return a concise summary, and disappear.
6
6
 
@@ -16,6 +16,21 @@ preserves the parent's flow better than a back-and-forth.
16
16
  You don't have session-management tools, so finishing the task yourself
17
17
  keeps the work together; nothing to delegate further to.
18
18
 
19
+ ## When the brief is silent
20
+
21
+ Missing constraints default to the conservative side:
22
+
23
+ - No commits, pushes, or tags unless the brief says so.
24
+ - Stay inside the task's stated scope — unrelated problems you notice go
25
+ in your summary as a note, not as extra fixes.
26
+ - Don't delete or move files, rewrite git history, or use force flags
27
+ unless asked.
28
+ - Workspace state you didn't create (uncommitted diffs, running
29
+ processes) belongs to someone else — work around it, don't clean it up.
30
+
31
+ Provisioning your own runtime (installing packages / CLI tools) stays
32
+ fine — see Shell.
33
+
19
34
  ## What you return
20
35
 
21
36
  A 1–3 sentence summary: what you did + the result the parent needs.
@@ -36,3 +51,6 @@ completion clutter the signal — concrete results land better.
36
51
  Long shell output buried in a summary hides the result. Save it to
37
52
  `<workspace>/.halo/tmp/` and reference the path; the summary stays
38
53
  scannable. Same for whole file contents.
54
+
55
+ A dead end is a valid result: report what's blocking and what you tried,
56
+ so the parent can re-route — don't dress partial work as complete.
@@ -1,13 +1,16 @@
1
1
  name: Executor
2
- description: Workhorse for self-contained tasks that would otherwise eat the parent's context — multi-file edits, 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.
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-4-6
5
+ id: global.anthropic.claude-sonnet-5
6
6
  endpoint: https://bedrock-runtime.us-east-1.amazonaws.com
7
7
  promptCaching: 1h
8
8
  thinking:
9
+ # xhigh is deliberate (above the model's registry default): the mid-tier
10
+ # model is cheap enough that more thinking is a good trade. The cost
11
+ # gradient vs deep-executor is signaled by the description, not effort.
9
12
  enabled: true
10
- effort: medium
13
+ effort: xhigh
11
14
  context:
12
15
  maxTokens: 200000
13
16
  compressAt: 0.8
@@ -35,6 +35,33 @@ secrets:
35
35
  # thinking.default → effort selected when Thinking is on (adaptive)
36
36
  # thinking.defaultBudgetTokens → budget selected when Thinking is on (manual)
37
37
  models:
38
+ - id: global.anthropic.claude-fable-5
39
+ displayName: Claude Fable 5
40
+ maxOutputTokens: 128000
41
+ capabilities:
42
+ image: true
43
+ video: false
44
+ audio: false
45
+ promptCaching:
46
+ default: 1h
47
+ ttlPresets:
48
+ - { value: 5m, label: 5min }
49
+ - { value: 1h, label: 1hour }
50
+ thinking:
51
+ # Fable 5 is the Mythos-class flagship (above Opus 4.8). Adaptive effort
52
+ # set confirmed by an invalid-value probe against Bedrock: the rejection
53
+ # named low / medium / high / xhigh / max — all five accepted on invoke.
54
+ # (Web sources claiming only low/medium/high are wrong.) 1M context, 128K output.
55
+ mode: adaptive
56
+ defaultEnabled: true
57
+ default: high # flagship-of-flagships — high by default; xhigh/max on demand
58
+ effortPresets:
59
+ - { value: low, label: Low }
60
+ - { value: medium, label: Medium }
61
+ - { value: high, label: High }
62
+ - { value: xhigh, label: XHigh }
63
+ - { value: max, label: Max }
64
+
38
65
  - id: global.anthropic.claude-sonnet-4-6
39
66
  displayName: Claude Sonnet 4.6
40
67
  maxOutputTokens: 64000
@@ -61,6 +88,32 @@ models:
61
88
  - { value: high, label: High }
62
89
  - { value: max, label: Max }
63
90
 
91
+ - id: global.anthropic.claude-sonnet-5
92
+ displayName: Claude Sonnet 5
93
+ maxOutputTokens: 64000
94
+ capabilities:
95
+ image: true
96
+ video: false
97
+ audio: false
98
+ promptCaching:
99
+ default: 1h
100
+ ttlPresets:
101
+ - { value: 5m, label: 5min }
102
+ - { value: 1h, label: 1hour }
103
+ thinking:
104
+ # Sonnet 5: adaptive thinking is always-on (can't be disabled); the
105
+ # effort set was confirmed via an invalid-value probe against Bedrock:
106
+ # low / medium / high / xhigh / max. 1M context, 128K output ceiling.
107
+ mode: adaptive
108
+ defaultEnabled: true
109
+ default: high
110
+ effortPresets:
111
+ - { value: low, label: Low }
112
+ - { value: medium, label: Medium }
113
+ - { value: high, label: High }
114
+ - { value: xhigh, label: XHigh }
115
+ - { value: max, label: Max }
116
+
64
117
  - id: global.anthropic.claude-opus-4-6-v1
65
118
  displayName: Claude Opus 4.6
66
119
  maxOutputTokens: 128000
@@ -0,0 +1,5 @@
1
+ ### Workspace `.halo/tmp/`
2
+ Runtime intermediates tied to the task (generated artifacts, saved shell
3
+ output, downloaded media, research notes) go in `<workspace>/.halo/tmp/`
4
+ — referenceable by path in reports and later sessions. OS `/tmp` is for
5
+ genuinely throwaway scratch only.
@@ -7,6 +7,10 @@ No user profile exists yet. Before doing anything else, have a brief, warm getti
7
7
  3. Ask how you should address them (their name or nickname)
8
8
  4. Ask about their preferred communication style — casual/formal, humor level, language preference, or anything else that matters to them
9
9
 
10
+ If the user opens with a concrete task, handle the task first — fold at
11
+ most one of these questions into your reply and collect the rest over
12
+ the following turns. Never block real work on introductions.
13
+
10
14
  Keep it natural and conversational — 2-3 exchanges, not a form. The 4 items above are topics, not a questionnaire; weave them in as the conversation flows, don't ask them one by one. Adapt to whatever language the user responds in.
11
15
 
12
16
  After collecting enough info, use `file_write` to create `~/.halo/global/USER.md` (global user profile, shared across all projects):
@@ -8,4 +8,6 @@ Each workspace has a `.halo/` directory whose persistent files outlive any singl
8
8
  | `.halo/INDEX.md` | Project documentation index | After project changes — remind the user to sync |
9
9
  | `.halo/memory/YYYY-MM-DD.md` | Past work worth keeping | When something should outlive this session |
10
10
 
11
+ Creating `.halo/INSTRUCTIONS.md` **replaces** the global INSTRUCTIONS.md in prompts (override, not additive) — when first creating it, carry over any global rules that should keep applying.
12
+
11
13
  **New workspace has no INDEX.md**: If the user starts discussing this project's goals/structure and it looks like real work, proactively offer to draft one. Don't ask for casual browsing.
@@ -81,6 +81,7 @@ Required from the user:
81
81
  | `label` | Slug — lowercase, hex/dash only (regex `^[a-z][a-z0-9-]*$`). Used for the skill id (`ask-<label>`), slash command (`/ask-<label>`), and settings namespace. Must not collide with an existing skill. Example: `sa-agent`, `prod-cluster`, `audit-ws`. |
82
82
  | `host` | Remote halo server hostname / IP. |
83
83
  | `port` | Remote halo server port. (halo kind only) |
84
+ | `scheme` | URL scheme to reach the remote — `http` or `https`. Default `http`; only set `https` if the remote is behind a TLS proxy. (halo kind only) |
84
85
  | `workspace` | Absolute path of the remote workspace on its server. |
85
86
  | `token` | Web-channel token from the remote halo's admin (Channels → Web → copy). Should be a `full` access token if you'll later want to override workspace per call. |
86
87
 
@@ -127,6 +128,7 @@ Both `SKILL.md.tmpl` and `config.yaml.tmpl` use `{{NAME}}` markers. Substitute t
127
128
  | `{{LABEL_DISPLAY}}` | display name (defaults to `label_display` or capitalized label) |
128
129
  | `{{HOST}}` | host |
129
130
  | `{{PORT}}` | port |
131
+ | `{{SCHEME}}` | scheme (`http` / `https`; default `http`) |
130
132
  | `{{WORKSPACE}}` | remote workspace path |
131
133
  | `{{SKILL_DIR}}` | absolute path of the new skill dir; use this in the `python3 …/ask.py` line so the cmd works regardless of cwd |
132
134
 
@@ -137,6 +139,7 @@ LABEL=sa-agent
137
139
  LABEL_DISPLAY="SA Agent"
138
140
  HOST=ec2-1-2-3-4.compute.amazonaws.com
139
141
  PORT=9527
142
+ SCHEME=http # or https if the remote is behind a TLS proxy
140
143
  WORKSPACE=/home/ubuntu/sa-agent
141
144
  SCOPE_DIR=$HOME/.halo/global/skills # or the workspace's .halo/skills
142
145
  SKILL_DIR=$SCOPE_DIR/ask-$LABEL
@@ -147,6 +150,7 @@ sed -e "s|{{LABEL}}|$LABEL|g" \
147
150
  -e "s|{{LABEL_DISPLAY}}|$LABEL_DISPLAY|g" \
148
151
  -e "s|{{HOST}}|$HOST|g" \
149
152
  -e "s|{{PORT}}|$PORT|g" \
153
+ -e "s|{{SCHEME}}|$SCHEME|g" \
150
154
  -e "s|{{WORKSPACE}}|$WORKSPACE|g" \
151
155
  -e "s|{{SKILL_DIR}}|$SKILL_DIR|g" \
152
156
  "$TPL/SKILL.md.tmpl" > "$SKILL_DIR/SKILL.md"
@@ -154,6 +158,7 @@ sed -e "s|{{LABEL}}|$LABEL|g" \
154
158
  -e "s|{{LABEL_DISPLAY}}|$LABEL_DISPLAY|g" \
155
159
  -e "s|{{HOST}}|$HOST|g" \
156
160
  -e "s|{{PORT}}|$PORT|g" \
161
+ -e "s|{{SCHEME}}|$SCHEME|g" \
157
162
  -e "s|{{WORKSPACE}}|$WORKSPACE|g" \
158
163
  "$TPL/config.yaml.tmpl" > "$SKILL_DIR/config.yaml"
159
164
  cp "$TPL/ask.py" "$SKILL_DIR/ask.py"
@@ -164,7 +169,7 @@ If any path contains `|` (rare), pick a different sed delimiter or use Python.
164
169
 
165
170
  ## Step 4 — write **all** params to settings.yaml
166
171
 
167
- Halo's shell_exec `{{<id>.params.X}}` substitution reads from `settings.yaml` and does NOT fall back to `config.yaml` `default:`. Config.yaml defaults are *only* used as form placeholders in the admin Settings UI. So at install time we must write every value the helper script needs (host, port, workspace, token, label) into settings.yaml — otherwise the SKILL.md's `python3 ask.py ... --host {{params.host}}` will pass through as a literal `{{...}}` to shell_exec and break.
172
+ Halo's shell_exec `{{<id>.params.X}}` substitution reads from `settings.yaml` and does NOT fall back to `config.yaml` `default:`. Config.yaml defaults are *only* used as form placeholders in the admin Settings UI. So at install time we must write every value the helper script needs (host, port, scheme, workspace, token, label) into settings.yaml — otherwise the SKILL.md's `python3 ask.py ... --host {{params.host}}` will pass through as a literal `{{...}}` to shell_exec and break.
168
173
 
169
174
  Choose the file by scope:
170
175
 
@@ -178,6 +183,7 @@ ask-<label>:
178
183
  params:
179
184
  host: <host>
180
185
  port: "<port>" # quote so YAML keeps it as a string
186
+ scheme: "<scheme>" # http (default) or https
181
187
  workspace: <workspace>
182
188
  label: <label_display>
183
189
  token: <token>
@@ -196,6 +202,7 @@ ns = data.setdefault("ask-<label>", {})
196
202
  params = ns.setdefault("params", {})
197
203
  params["host"] = "<host>"
198
204
  params["port"] = "<port>"
205
+ params["scheme"] = "<scheme>"
199
206
  params["workspace"] = "<workspace>"
200
207
  params["label"] = "<label_display>"
201
208
  params["token"] = "<token>"
@@ -28,6 +28,7 @@ shell_exec: python3 {{SKILL_DIR}}/ask.py \
28
28
  "<the user's question, in the language they asked it>" \
29
29
  --host {{params.host}} \
30
30
  --port {{params.port}} \
31
+ --scheme {{params.scheme}} \
31
32
  --workspace {{params.workspace}} \
32
33
  --token {{params.token}}
33
34
  ```
@@ -51,7 +52,7 @@ Sequences of related questions ("what's the AWS bill", "now break down Bedrock",
51
52
  ```bash
52
53
  shell_exec: python3 {{SKILL_DIR}}/ask.py \
53
54
  "<follow-up question>" \
54
- --host {{params.host}} --port {{params.port}} \
55
+ --host {{params.host}} --port {{params.port}} --scheme {{params.scheme}} \
55
56
  --workspace {{params.workspace}} --token {{params.token}} \
56
57
  --session-id <id-from-previous-call>
57
58
  ```
@@ -25,6 +25,7 @@ def main() -> None:
25
25
  help="halo: remote halo server via `halo acp`; claude: local Claude Code via `claude-agent-acp`; kiro: local Kiro via `kiro-cli acp`.")
26
26
  p.add_argument("--host")
27
27
  p.add_argument("--port")
28
+ p.add_argument("--scheme", default="http")
28
29
  p.add_argument("--token")
29
30
  p.add_argument("--workspace")
30
31
  p.add_argument("--cwd", default=None, help="claude kind: working directory for the session (defaults to CWD).")
@@ -58,7 +59,7 @@ def main() -> None:
58
59
  if shutil.which(halo) is None and not os.path.isabs(halo):
59
60
  fail(f"`{halo}` not found in PATH. Install @turmind/halo or set --halo-bin.", 2)
60
61
  cmd = [halo, "acp", "--host", args.host, "--port", str(args.port),
61
- "--token", args.token, "--workspace", args.workspace]
62
+ "--scheme", args.scheme, "--token", args.token, "--workspace", args.workspace]
62
63
  # Drop `--agent-id` when blank OR an unsubstituted `{{...}}` literal.
63
64
  # Both mean "user hasn't configured this optional field" — the first
64
65
  # because settings.yaml has `agent_id: ""`, the second because the
@@ -12,6 +12,11 @@ params:
12
12
  description_zh: 远端 halo server 端口。
13
13
  default: "{{PORT}}"
14
14
 
15
+ - key: scheme
16
+ description: URL scheme to reach the remote halo server — http or https.
17
+ description_zh: 连接远端 halo server 的协议 — http 或 https。
18
+ default: "{{SCHEME}}"
19
+
15
20
  - key: workspace
16
21
  description: Absolute path of the remote workspace to drive.
17
22
  description_zh: 要驱动的远端 workspace 绝对路径。
@@ -1 +0,0 @@
1
- "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[298],{8808:(e,t,o)=>{o.d(t,{I18nProvider:()=>d,useI18n:()=>c,useT:()=>l});var n=o(69625),a=o(43853),s=o(17246);function r(e){return"zh"===e?"zh-CN":"en-US"}let i=(0,a.createContext)({lang:"en",bcp:"en-US",t:e=>e,setLang:()=>{},refreshFromServer:async()=>{}});function c(){return(0,a.useContext)(i)}function l(){return(0,a.useContext)(i).t}function d(e){let{en:t,zh:o,children:c}=e,[l,d]=(0,a.useState)("en");(0,a.useEffect)(()=>{d(function(){let e=localStorage.getItem("halo_lang");return"zh"===e||"en"===e?e:navigator.language.startsWith("zh")?"zh":"en"}())},[]);let u=(0,a.useCallback)(async()=>{try{var e,t,o;let n=(await s.F.settings.getSchema()).sections.find(e=>"general"===e.namespaceId),a=null==n?void 0:n.fields.find(e=>"language"===e.key),r=(o=null!=(t=null!=(e=null==a?void 0:a.value)?e:null==a?void 0:a.default)?t:null,"string"==typeof o&&o.toLowerCase().startsWith("zh")?"zh":"en");d(r),localStorage.setItem("halo_lang",r)}catch(e){}},[]);(0,a.useEffect)(()=>{u()},[u]);let p=(0,a.useCallback)(e=>{d(e),localStorage.setItem("halo_lang",e),s.F.settings.patch("global","general.language",r(e)).catch(()=>{})},[]),g=(0,a.useCallback)((e,n)=>{var a,s;let r=null!=(s=null!=(a=("zh"===l?o:t)[e])?a:t[e])?s:e;if(n)for(let[e,t]of Object.entries(n))r=r.replaceAll("{".concat(e,"}"),String(t));return r},[l,t,o]);return(0,n.jsx)(i.Provider,{value:{lang:l,bcp:r(l),t:g,setLang:p,refreshFromServer:u},children:c})}},17246:(e,t,o)=>{o.d(t,{F:()=>s});let n="/api";async function a(e,t){let o=await fetch("".concat(n).concat(e),{headers:{"Content-Type":"application/json",...null==t?void 0:t.headers},...t});if(!o.ok){let e=await o.text().catch(()=>"");throw Error("API error ".concat(o.status,": ").concat(e||o.statusText))}return o.json()}let s={health:()=>fetch("".concat(n,"/health")).then(e=>e.json()),chat:{send:(e,t,o)=>fetch("".concat(n,"/chat"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sessionId:e,projectId:t,message:o})}),getHistory:e=>a("/chat/".concat(e,"/history"))},tasks:{get:e=>a("/tasks/".concat(e)),approve:e=>a("/tasks/".concat(e,"/approve"),{method:"POST"}),reject:(e,t)=>a("/tasks/".concat(e,"/reject"),{method:"POST",body:JSON.stringify({feedback:t})})},agents:{list:()=>a("/agents"),get:e=>a("/agents/".concat(e)),create:e=>a("/agents",{method:"POST",body:JSON.stringify(e)}),update:(e,t)=>a("/agents/".concat(e),{method:"PUT",body:JSON.stringify(t)}),remove:e=>a("/agents/".concat(e),{method:"DELETE"})},fs:{home:()=>a("/fs/home"),exists:e=>a("/fs/exists?path=".concat(encodeURIComponent(e))),browse:e=>a("/fs/browse?path=".concat(encodeURIComponent(e))),resolveWorkspace:e=>a("/fs/workspace/resolve",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:e})})},files:{read:(e,t)=>a("/files?path=".concat(encodeURIComponent(e),"&projectId=").concat(encodeURIComponent(t))),stat:(e,t)=>a("/files/stat?path=".concat(encodeURIComponent(e),"&projectId=").concat(encodeURIComponent(t))),tree(e,t){let o=new URLSearchParams;return o.set("projectId",e),t&&o.set("path",t),a("/files/tree?".concat(o.toString()))},search(e,t,o,n){let s=new URLSearchParams;return s.set("projectId",e),s.set("q",t),o&&s.set("limit",String(o)),n&&s.set("dirsOnly","1"),a("/files/search?".concat(s.toString()))},upload(e,t,o,a){let s=new FormData;for(let n of(s.append("projectId",t),o&&s.append("targetDir",o),e))s.append("files",n);return new Promise((e,t)=>{let o=new XMLHttpRequest;o.open("POST","".concat(n,"/files/upload")),o.withCredentials=!0,o.upload.onprogress=e=>{a&&(e.lengthComputable?a(e.loaded,e.total):a(e.loaded,0))},o.onload=()=>{if(o.status>=200&&o.status<300)try{e(JSON.parse(o.responseText))}catch(e){t(e)}else t(Error("Upload failed: ".concat(o.status," ").concat(o.statusText)))},o.onerror=()=>t(Error("Upload failed: network error")),o.onabort=()=>t(Error("Upload aborted")),o.send(s)})},save:(e,t,o)=>a("/files",{method:"PUT",body:JSON.stringify({path:e,content:t,projectId:o})}),create:(e,t)=>a("/files/new",{method:"POST",body:JSON.stringify({path:e,projectId:t})}),mkdir:(e,t)=>a("/files/mkdir",{method:"POST",body:JSON.stringify({path:e,projectId:t})}),rename:(e,t,o)=>a("/files/rename",{method:"POST",body:JSON.stringify({oldPath:e,newPath:t,projectId:o})}),remove:(e,t)=>a("/files?path=".concat(encodeURIComponent(e),"&projectId=").concat(encodeURIComponent(t)),{method:"DELETE"}),downloadUrl:(e,t)=>"/api/files/download?path=".concat(encodeURIComponent(e),"&projectId=").concat(encodeURIComponent(t)),viewUrl:(e,t)=>"/api/files/download?path=".concat(encodeURIComponent(e),"&projectId=").concat(encodeURIComponent(t),"&inline=1"),diff:(e,t)=>a("/files/diff?path=".concat(encodeURIComponent(e),"&projectId=").concat(encodeURIComponent(t)))},git:{status:e=>a("/git/status?projectId=".concat(encodeURIComponent(e))),ignored:e=>a("/git/ignored?projectId=".concat(encodeURIComponent(e))),diff(e,t,o,n,s){let r=new URLSearchParams;return r.set("projectId",e),r.set("path",t),r.set("staged",o?"1":"0"),n&&r.set("from",n),s&&r.set("commit",s),a("/git/diff?".concat(r.toString()))},log(e,t){let o=new URLSearchParams({projectId:e});return t&&o.set("limit",String(t)),a("/git/log?".concat(o.toString()))},commitFiles(e,t){let o=new URLSearchParams({projectId:e,hash:t});return a("/git/commit-files?".concat(o.toString()))},stage:(e,t)=>a("/git/stage",{method:"POST",body:JSON.stringify({projectId:e,paths:t})}),unstage:(e,t)=>a("/git/unstage",{method:"POST",body:JSON.stringify({projectId:e,paths:t})}),commit:(e,t)=>a("/git/commit",{method:"POST",body:JSON.stringify({projectId:e,message:t})}),push:e=>a("/git/push",{method:"POST",body:JSON.stringify({projectId:e})}),pull:e=>a("/git/pull",{method:"POST",body:JSON.stringify({projectId:e})}),init:e=>a("/git/init",{method:"POST",body:JSON.stringify({projectId:e})}),remotes:e=>a("/git/remotes?projectId=".concat(encodeURIComponent(e))),addRemote:(e,t,o)=>a("/git/remote",{method:"POST",body:JSON.stringify({projectId:e,url:t,name:o})}),getCredentials:()=>a("/git/credentials"),saveCredentials:e=>a("/git/credentials",{method:"POST",body:JSON.stringify(e)}),deleteCredential:e=>a("/git/credentials/".concat(encodeURIComponent(e)),{method:"DELETE"}),sshKeys:()=>a("/git/ssh/keys"),sshAgent:()=>a("/git/ssh/agent"),unlockSshKey:e=>a("/git/ssh/unlock",{method:"POST",body:JSON.stringify(e)}),getRemoteProtocol:e=>a("/git/remote/protocol?projectId=".concat(encodeURIComponent(e))),setRemoteProtocol:(e,t)=>a("/git/remote/protocol",{method:"POST",body:JSON.stringify({projectId:e,to:t})})},agentConfigs:{list(e){let t=e?"?projectId=".concat(encodeURIComponent(e)):"";return a("/agent-configs".concat(t))},create:e=>a("/agent-configs",{method:"POST",body:JSON.stringify(e)}),getYaml(e,t){let o=new URLSearchParams;(null==t?void 0:t.scope)&&o.set("scope",t.scope),(null==t?void 0:t.projectId)&&o.set("projectId",t.projectId);let n=o.toString()?"?".concat(o):"";return a("/agent-configs/".concat(e,"/yaml").concat(n))},saveYaml:(e,t,o)=>a("/agent-configs/".concat(e,"/yaml"),{method:"PUT",body:JSON.stringify({yaml:t,scope:null==o?void 0:o.scope,projectId:null==o?void 0:o.projectId})}),remove(e,t){let o=new URLSearchParams;(null==t?void 0:t.scope)&&o.set("scope",t.scope),(null==t?void 0:t.projectId)&&o.set("projectId",t.projectId);let n=o.toString()?"?".concat(o):"";return a("/agent-configs/".concat(e).concat(n),{method:"DELETE"})},toggle(e,t){let o=new URLSearchParams;(null==t?void 0:t.scope)&&o.set("scope",t.scope),(null==t?void 0:t.projectId)&&o.set("projectId",t.projectId);let n=o.toString()?"?".concat(o):"";return a("/agent-configs/".concat(e,"/toggle").concat(n),{method:"PATCH"})},tools:()=>a("/agent-configs/tools"),models:()=>a("/agent-configs/models"),getMdFile(e,t,o){let n=new URLSearchParams;(null==o?void 0:o.scope)&&n.set("scope",o.scope),(null==o?void 0:o.projectId)&&n.set("projectId",o.projectId);let s=n.toString()?"?".concat(n):"";return a("/agent-configs/".concat(e,"/md/").concat(t).concat(s))},saveMdFile:(e,t,o,n)=>a("/agent-configs/".concat(e,"/md/").concat(t),{method:"PUT",body:JSON.stringify({content:o,scope:null==n?void 0:n.scope,projectId:null==n?void 0:n.projectId})}),getMdAll(e,t){let o=new URLSearchParams;(null==t?void 0:t.scope)&&o.set("scope",t.scope),(null==t?void 0:t.projectId)&&o.set("projectId",t.projectId);let n=o.toString()?"?".concat(o):"";return a("/agent-configs/".concat(e,"/md-all").concat(n))},listSessions(e,t){let o=new URLSearchParams;(null==t?void 0:t.projectId)&&o.set("projectId",t.projectId),(null==t?void 0:t.source)&&o.set("source",t.source);let n=o.toString()?"?".concat(o):"";return a("/agent-configs/".concat(e,"/sessions").concat(n))},getSession(e,t,o){let n=new URLSearchParams;(null==o?void 0:o.projectId)&&n.set("projectId",o.projectId),(null==o?void 0:o.source)&&n.set("source",o.source);let s=n.toString()?"?".concat(n):"";return a("/agent-configs/".concat(e,"/sessions/").concat(t).concat(s))},saveSession:(e,t)=>a("/agent-configs/".concat(e,"/sessions"),{method:"POST",body:JSON.stringify(t)}),deleteSession(e,t,o){let n=new URLSearchParams;(null==o?void 0:o.projectId)&&n.set("projectId",o.projectId),(null==o?void 0:o.source)&&n.set("source",o.source);let s=n.toString()?"?".concat(n):"";return a("/agent-configs/".concat(e,"/sessions/").concat(t).concat(s),{method:"DELETE"})},deleteAllSessions(e,t){let o=new URLSearchParams;(null==t?void 0:t.projectId)&&o.set("projectId",t.projectId),(null==t?void 0:t.source)&&o.set("source",t.source),o.set("all","1");let n="?".concat(o);return a("/agent-configs/".concat(e,"/sessions").concat(n),{method:"DELETE"})}},sessionLogs:{list(e,t){let o=new URLSearchParams;e&&o.set("projectId",e),(null==t?void 0:t.includeArchived)&&o.set("includeArchived","1"),(null==t?void 0:t.parentId)!==void 0&&o.set("parentId",t.parentId),(null==t?void 0:t.rootOnly)&&o.set("rootOnly","1"),(null==t?void 0:t.cursor)!==void 0&&o.set("cursor",String(t.cursor)),(null==t?void 0:t.limit)!==void 0&&o.set("limit",String(t.limit));let n=o.toString()?"?".concat(o):"";return a("/sessions/logs".concat(n))},get(e,t){let o=new URLSearchParams;t&&o.set("projectId",t);let n=o.toString()?"?".concat(o):"";return a("/sessions/logs/".concat(e).concat(n))},delete(e,t){let o=new URLSearchParams;t&&o.set("projectId",t);let n=o.toString()?"?".concat(o):"";return a("/sessions/logs/".concat(e).concat(n),{method:"DELETE"})},rename(e,t,o){let n=new URLSearchParams;o&&n.set("projectId",o);let s=n.toString()?"?".concat(n):"";return a("/sessions/logs/".concat(e).concat(s),{method:"PATCH",body:JSON.stringify({title:t})})}},commands:{list(e,t,o){let n=new URLSearchParams;e&&n.set("projectId",e),t&&n.set("sessionId",t),o&&n.set("agentId",o);let s=n.toString()?"?".concat(n.toString()):"";return a("/commands".concat(s))}},skills:{list(e){let t=e?"?projectId=".concat(encodeURIComponent(e)):"";return a("/skills".concat(t))},create:e=>a("/skills",{method:"POST",body:JSON.stringify(e)}),remove(e,t){let o=new URLSearchParams;(null==t?void 0:t.scope)&&o.set("scope",t.scope),(null==t?void 0:t.projectId)&&o.set("projectId",t.projectId);let n=o.toString()?"?".concat(o):"";return a("/skills/".concat(e).concat(n),{method:"DELETE"})},toggle(e,t){let o=new URLSearchParams;(null==t?void 0:t.scope)&&o.set("scope",t.scope),(null==t?void 0:t.projectId)&&o.set("projectId",t.projectId);let n=o.toString()?"?".concat(o):"";return a("/skills/".concat(e,"/toggle").concat(n),{method:"PATCH"})}},projects:{list:()=>a("/projects"),create:e=>a("/projects",{method:"POST",body:JSON.stringify({name:e})}),get:e=>a("/projects/".concat(e))},weixin:{listAccounts:()=>a("/weixin/accounts"),startLogin:e=>a("/weixin/login/start",{method:"POST",body:JSON.stringify({sessionKey:e})}),waitLogin:e=>a("/weixin/login/wait",{method:"POST",body:JSON.stringify(e)}),updateAccount:(e,t)=>a("/weixin/accounts/".concat(encodeURIComponent(e)),{method:"PATCH",body:JSON.stringify(t)}),deleteAccount:e=>a("/weixin/accounts/".concat(encodeURIComponent(e)),{method:"DELETE"})},telegram:{listAccounts:()=>a("/telegram/accounts"),createAccount:e=>a("/telegram/accounts",{method:"POST",body:JSON.stringify(e)}),updateAccount:(e,t)=>a("/telegram/accounts/".concat(encodeURIComponent(e)),{method:"PATCH",body:JSON.stringify(t)}),deleteAccount:e=>a("/telegram/accounts/".concat(encodeURIComponent(e)),{method:"DELETE"})},web:{listAccounts:()=>a("/web/accounts"),createAccount:e=>a("/web/accounts",{method:"POST",body:JSON.stringify(e)}),updateAccount:(e,t)=>a("/web/accounts/".concat(encodeURIComponent(e)),{method:"PATCH",body:JSON.stringify(t)}),deleteAccount:e=>a("/web/accounts/".concat(encodeURIComponent(e)),{method:"DELETE"})},slack:{listAccounts:()=>a("/slack/accounts"),createAccount:e=>a("/slack/accounts",{method:"POST",body:JSON.stringify(e)}),updateAccount:(e,t)=>a("/slack/accounts/".concat(encodeURIComponent(e)),{method:"PATCH",body:JSON.stringify(t)}),deleteAccount:e=>a("/slack/accounts/".concat(encodeURIComponent(e)),{method:"DELETE"}),searchTargets:(e,t)=>a("/slack/accounts/".concat(encodeURIComponent(e),"/search?q=").concat(encodeURIComponent(t)))},feishu:{listAccounts:()=>a("/feishu/accounts"),createAccount:e=>a("/feishu/accounts",{method:"POST",body:JSON.stringify(e)}),updateAccount:(e,t)=>a("/feishu/accounts/".concat(encodeURIComponent(e)),{method:"PATCH",body:JSON.stringify(t)}),deleteAccount:e=>a("/feishu/accounts/".concat(encodeURIComponent(e)),{method:"DELETE"}),searchTargets:(e,t)=>a("/feishu/accounts/".concat(encodeURIComponent(e),"/search?q=").concat(encodeURIComponent(t)))},settings:{get(e){let t=e?"?projectId=".concat(encodeURIComponent(e)):"";return a("/settings".concat(t))},getSchema(e){let t=e?"?projectId=".concat(encodeURIComponent(e)):"";return a("/settings/schema".concat(t))},save:(e,t,o)=>a("/settings",{method:"PUT",body:JSON.stringify({scope:e,projectId:o,settings:t})}),patch:(e,t,o,n)=>a("/settings",{method:"PATCH",body:JSON.stringify({scope:e,projectId:n,key:t,value:o})}),remove:(e,t,o)=>a("/settings",{method:"DELETE",body:JSON.stringify({scope:e,projectId:o,key:t})})},evolution:{listRuns(e){let t=new URLSearchParams;(null==e?void 0:e.archived)&&t.set("archived","1"),(null==e?void 0:e.limit)!==void 0&&t.set("limit",String(e.limit)),(null==e?void 0:e.before)!==void 0&&t.set("before",String(e.before));let o=t.toString()?"?".concat(t):"";return a("/evolution/runs".concat(o))},getRun:e=>a("/evolution/runs/".concat(encodeURIComponent(e))),approve:(e,t)=>a("/evolution/runs/".concat(encodeURIComponent(e),"/approve"),{method:"POST",body:JSON.stringify({reviewerHint:t})}),reject:e=>a("/evolution/runs/".concat(encodeURIComponent(e),"/reject"),{method:"POST",body:"{}"}),addHint:(e,t)=>a("/evolution/runs/".concat(encodeURIComponent(e),"/hint"),{method:"POST",body:JSON.stringify({hint:t})}),retry:(e,t)=>a("/evolution/runs/".concat(encodeURIComponent(e),"/retry"),{method:"POST",body:JSON.stringify({hint:t})}),delete:e=>a("/evolution/runs/".concat(encodeURIComponent(e)),{method:"DELETE"})},cron:{listJobs(e){let t=new URLSearchParams;(null==e?void 0:e.limit)!==void 0&&t.set("limit",String(e.limit)),(null==e?void 0:e.before)!==void 0&&t.set("before",String(e.before));let o=t.toString()?"?".concat(t):"";return a("/cron/jobs".concat(o))},createJob:e=>a("/cron/jobs",{method:"POST",body:JSON.stringify(e)}),updateJob:(e,t)=>a("/cron/jobs/".concat(encodeURIComponent(e)),{method:"PUT",body:JSON.stringify(t)}),deleteJob:e=>a("/cron/jobs/".concat(encodeURIComponent(e)),{method:"DELETE"}),runNow:e=>a("/cron/jobs/".concat(encodeURIComponent(e),"/run-now"),{method:"POST",body:"{}"}),listRuns(e,t){let o=new URLSearchParams;(null==t?void 0:t.limit)&&o.set("limit",String(t.limit)),(null==t?void 0:t.before)&&o.set("before",t.before);let n=o.toString()?"?".concat(o):"";return a("/cron/jobs/".concat(encodeURIComponent(e),"/runs").concat(n))},getRunLog:e=>a("/cron/runs/".concat(encodeURIComponent(e),"/log")),listChannelTargets:()=>a("/cron/channel-targets"),meta:()=>a("/cron/meta")}}},26967:(e,t,o)=>{o.d(t,{AppI18nProvider:()=>i});var n=o(69625),a=o(8808);let s={"channels.weixin":"WeChat","tg.title":"Telegram Bot","tg.desc":"Add a bot created by BotFather. Telegram messages will go to the bound workspace.","tg.add":"Add Bot","tg.loading":"Loading...","tg.empty":"No bots yet. Create one with @BotFather on Telegram, then click Add above.","tg.saveFailed":"Save failed: {error}","tg.switchFailed":"Switch failed: {error}","tg.confirmDelete":"Delete bot @{name}?","tg.deleteFailed":"Delete failed: {error}","tg.label":"Name","tg.labelPlaceholder":"Bot name","tg.workspace":"Bind workspace (absolute path)","tg.accessLevel":"Access level","tg.readonly":"Readonly — read only (default)","tg.wsWrite":"Workspace — read-write within workspace","tg.full":"Full — full access","tg.allowedUsers":"Allowed users (comma-separated user_id or @username, empty = no limit)","tg.save":"Save","tg.cancel":"Cancel","tg.whitelist":"Whitelist","tg.pathMissing":"(path does not exist)","tg.disable":"Disable","tg.enable":"Enable","tg.edit":"Edit","tg.delete":"Delete","tg.addTitle":"Add Telegram Bot","tg.tokenLabel":"Bot Token (from @BotFather)","tg.bindWorkspace":"Bind to workspace","tg.nameOptional":"Name (optional)","tg.namePlaceholder":"e.g. SA Agent","tg.allowedUsersOptional":"Allowed users (optional, comma-separated)","tg.allowedUsersHint":"user_id or @username, empty = no limit","tg.addBtn":"Add","slack.title":"Slack Bot","slack.desc":"Add a Slack app with Socket Mode enabled. The bot listens via a long-lived ws connection — no public webhook needed.","slack.add":"Add Bot","slack.loading":"Loading...","slack.empty":"No Slack bots yet. Create an app at api.slack.com, enable Socket Mode, then click Add above.","slack.saveFailed":"Save failed: {error}","slack.switchFailed":"Switch failed: {error}","slack.confirmDelete":"Delete bot for team {name}?","slack.deleteFailed":"Delete failed: {error}","slack.label":"Name","slack.labelPlaceholder":"Bot name","slack.workspace":"Bind workspace (absolute path)","slack.accessLevel":"Access level","slack.readonly":"Readonly — read only (default)","slack.wsWrite":"Workspace — read-write within workspace","slack.full":"Full — full access","slack.save":"Save","slack.cancel":"Cancel","slack.pathMissing":"(path does not exist)","slack.disable":"Disable","slack.enable":"Enable","slack.edit":"Edit","slack.delete":"Delete","slack.addTitle":"Add Slack Bot","slack.botTokenLabel":"Bot Token (xoxb-…)","slack.botTokenHint":"OAuth & Permissions → Bot User OAuth Token","slack.appTokenLabel":"App Token (xapp-…)","slack.appTokenHint":"Basic Information → App-Level Tokens (with connections:write)","slack.bindWorkspace":"Bind to workspace","slack.nameOptional":"Name (optional)","slack.namePlaceholder":"e.g. SA Agent","slack.addBtn":"Add","feishu.title":"Feishu Bot","feishu.desc":"Add a Feishu (Lark) app with long-connect events enabled. The bot listens via a wss connection — no public webhook needed.","feishu.add":"Add Bot","feishu.loading":"Loading...","feishu.empty":"No Feishu bots yet. Create an app in the Feishu open platform, enable long-connect events, then click Add above.","feishu.saveFailed":"Save failed: {error}","feishu.switchFailed":"Switch failed: {error}","feishu.confirmDelete":"Delete bot for app {name}?","feishu.deleteFailed":"Delete failed: {error}","feishu.label":"Name","feishu.labelPlaceholder":"Bot name","feishu.workspace":"Bind workspace (absolute path)","feishu.accessLevel":"Access level","feishu.readonly":"Readonly — read only (default)","feishu.wsWrite":"Workspace — read-write within workspace","feishu.full":"Full — full access","feishu.save":"Save","feishu.cancel":"Cancel","feishu.pathMissing":"(path does not exist)","feishu.disable":"Disable","feishu.enable":"Enable","feishu.edit":"Edit","feishu.delete":"Delete","feishu.addTitle":"Add Feishu Bot","feishu.appIdLabel":"App ID","feishu.appSecretLabel":"App Secret","feishu.botOpenIdLabel":"Bot Open ID","feishu.botOpenIdHint":"The bot's own open_id, used to detect mentions in groups","feishu.verificationTokenLabel":"Verification Token (optional)","feishu.encryptKeyLabel":"Encrypt Key (optional)","feishu.bindWorkspace":"Bind to workspace","feishu.nameOptional":"Name (optional)","feishu.namePlaceholder":"e.g. SA Agent","feishu.addBtn":"Add","web.title":"Web Access","web.desc":"Create tokens for web frontend access. Each token binds to one workspace.","web.create":"Create Access","web.loading":"Loading...","web.empty":"No web access yet. Click Create above to generate a token.","web.saveFailed":"Save failed: {error}","web.switchFailed":"Switch failed: {error}","web.confirmDelete":"Delete this access? The token will be invalidated.","web.deleteFailed":"Delete failed: {error}","web.label":"Name","web.labelPlaceholder":"Access name","web.workspace":"Bind workspace (absolute path)","web.accessLevel":"Access level","web.readonly":"Readonly — read only (default)","web.wsWrite":"Workspace — read-write within workspace","web.full":"Full — full access","web.observer":"Observer — global read-only (dashboards/metrics)","web.save":"Save","web.cancel":"Cancel","web.pathMissing":"(path does not exist)","web.copied":"Copied","web.copyToken":"Copy Token","web.disable":"Disable","web.enable":"Enable","web.edit":"Edit","web.delete":"Delete","web.createSuccess":"Created successfully","web.tokenNotice":"Save this token (shown only once):","web.done":"Done","web.createTitle":"Create Web Access","web.bindWorkspace":"Bind to workspace","web.nameOptional":"Name (optional)","web.namePlaceholder":"e.g. SA Demo","web.createBtn":"Create","wx.title":"WeChat Bot","wx.desc":"After scanning, WeChat messages will go to the bound workspace session.","wx.add":"Add Bot","wx.loading":"Loading...","wx.empty":'No bots yet. Click "Add Bot" above to scan with WeChat.',"wx.saveFailed":"Save failed: {error}","wx.switchFailed":"Switch failed: {error}","wx.confirmDelete":'Delete bot "{name}"? Friends will get no response.',"wx.deleteFailed":"Delete failed: {error}","wx.label":"Name","wx.labelPlaceholder":"Bot name","wx.workspace":"Bind workspace (absolute path)","wx.accessLevel":"Access level","wx.readonly":"Readonly — read workspace files / grep / glob (default)","wx.wsWrite":"Workspace — read-write within workspace","wx.full":"Full — full access, including write and shell","wx.readonlyHint":"Readonly: WeChat users can only let agent read files/search, cannot write, run commands or access network. Sub-agents inherit the same level.","wx.save":"Save","wx.cancel":"Cancel","wx.disable":"Disable","wx.enable":"Enable","wx.rescan":"Re-scan (refresh token)","wx.edit":"Edit","wx.delete":"Delete","wx.generatingQr":"Generating QR code...","wx.pathRequired":"Workspace must be an absolute path","wx.qrFailed":"Failed to generate QR code","wx.scanPrompt":"Scan with WeChat","wx.connected":"Connected ({id})","wx.loginFailed":"Login failed","wx.rescanBtn":"Re-scan","wx.addTitle":"Add WeChat Bot","wx.bindWorkspace":"Bind to workspace","wx.bindHint":"WeChat messages will go to this workspace session, visible in web panel","wx.nameOptional":"Name (optional)","wx.namePlaceholder":"e.g. Work Assistant","wx.readonlyFull":"Full — same as web","wx.readonlyShareHint":"Readonly: WeChat users cannot write files, run shell, or access network. Sub-agents inherit. Strongly recommended for sharing with clients.","wx.nextStep":"Next: Scan","wx.scanTitle":"Scan with WeChat","wx.bindTo":"Bind to","wx.restart":"Start over","media.image":"Image","media.video":"Video","media.voice":"Voice","media.file":"File","chat.imageSent":"[Sent {n} image(s)]","agent.agents":"Agents","agent.selectToEdit":"Select an agent to edit","agent.back":"Back","agent.test":"Test","agent.edit":"Edit","agent.editHint":"Open agent.yaml + AGENT.md in a mini workspace","agent.saving":"saving…","agent.loading":"Loading...","agent.mdPreviewHint":'preview (read-only — edit via "Edit" above)',"agent.basic":"Basic","agent.name":"name","agent.description":"description","agent.priority":"priority","agent.model":"Model","agent.provider":"provider","agent.modelId":"id","agent.endpoint":"endpoint","agent.maxTokens":"maxTokens","agent.maxTokensDefault":"Default: {max} (model max)","agent.maxTokensAuto":"Leave blank for model default","agent.systemPrompt":"System Prompt","agent.systemPromptHint":"(fallback if no AGENT.md)","agent.systemPromptPlaceholder":"Instructions for this agent...","agent.context":"Context","agent.team":"Team","agent.teamHint":"(pick agents to delegate to — selecting any grants the session/delegation tools; none = no delegation)","agent.loadingTools":"Loading tools...","agent.noSkills":"No skills available. Create skills first.","skill.skills":"Skills","skill.noSkills":"No skills yet. Click + on a section to create one.","settings.general":"General","settings.params":"Params","settings.emptyParams":"No params yet. Add entries under params.x.y in YAML view — they can be referenced as {{params.x.y}} in AGENT.md / SKILL.md.","settings.emptyGeneral":"No settings in this section yet. Switch to YAML view to add manually.","settings.nav.system":"System","settings.nav.providers":"Model Providers","settings.nav.skills":"Skills","settings.nav.agents":"Agents","settings.nav.orphans":"Orphans","settings.nav.unclaimed":"Unclaimed values","settings.section.namespace":"Namespace","settings.section.declaredBy":"Declared by","settings.section.declaredBy.general":"General","settings.section.declaredBy.provider":"Provider","settings.section.declaredBy.skill":"Skill","settings.section.declaredBy.agent":"Agent","settings.section.settings":"Settings","settings.section.params":"Params","settings.section.secrets":"Secrets","settings.section.secretsHint":"Server-side only. Never injected into agent prompts or shell commands. {placeholder} placeholders are pulled from the process environment at read time.","settings.field.reset":"Reset","settings.field.resetTitleWorkspace":"Remove workspace override (inherit from global)","settings.field.resetTitleGlobal":"Remove global value (revert to default)","settings.source.workspace":"workspace","settings.source.global":"global","settings.source.inherited":"inherited from global","settings.source.unset":"unset","settings.orphans.title":"Unclaimed values","settings.orphans.intro":"These values exist in settings.yaml but no provider or skill currently declares them. They might be left over from an uninstalled package — or the declaration was removed. Safe to keep (they re-attach automatically if their namespace comes back) or remove.","settings.orphans.remove":"Remove","settings.orphans.confirm":"Remove orphan {key}? This deletes the value from settings.yaml.","settings.empty":"No section selected.","settings.loading":"Loading…","nav.explorer":"Explorer","nav.sourceControl":"Source Control","nav.sessions":"Sessions","nav.channels":"Channels","nav.settings":"Settings","nav.canvas":"Canvas","nav.chat":"CHAT","nav.terminal":"TERMINAL","sc.refresh":"Refresh","sc.staged":"Staged Changes","sc.changes":"Changes","sc.stageAll":"Stage All Changes","sc.unstageAll":"Unstage All Changes","sc.commit":"Commit","sc.commitPlaceholder":"Message (Ctrl+Enter to commit)","sc.push":"Push","sc.pull":"Pull","sc.clean":"No changes","sc.initRepo":"Initialize Repository","sc.initRepoDesc":"Start tracking this folder with git. Creates a .gitignore and an initial commit.","sc.initializing":"Initializing…","sc.addRemoteDesc":"No remote configured. Add the URL of an empty repository you created on GitHub/GitLab to push to.","sc.remotePlaceholder":"https://github.com/user/repo.git","sc.addRemote":"Add Remote","sc.adding":"Adding…","sc.authError":"Push failed: Git credentials needed. Configure them, then push again.","sc.openCreds":"Open credential settings","sc.noWorkspace":"Open a folder to use source control","sc.selectFile":"Select a file to view its diff","sc.loadingDiff":"Loading diff…","sc.diffStaged":"(staged)","sc.diffWorking":"(working tree)","sc.viewChanges":"Changes","sc.viewGraph":"Graph","sc.noCommits":"No commits yet","sc.noCommitFiles":"No files changed","sc.pushed":"Pushed to remote","sc.unpushed":"Local only — not pushed","sc.cred.title":"Git Credentials","sc.cred.tabHttps":"HTTPS","sc.cred.tabSsh":"SSH","sc.cred.httpsDesc":"Sign in with your GitHub/GitLab username and a Personal Access Token (not your account password). Stored on disk — configure once, survives restarts.","sc.cred.configuredList":"Configured credentials","sc.cred.noCredsYet":"No credentials configured yet","sc.cred.delete":"Delete","sc.cred.confirmDelete":"Confirm delete?","sc.cred.addNew":"Add credential","sc.cred.host":"Host","sc.cred.username":"Username","sc.cred.token":"Personal Access Token","sc.cred.tokenHint":"Stored locally with 0600 permissions, never displayed again.","sc.cred.tokenLink":"How to create a PAT","sc.cred.required":"Host, username and token are all required.","sc.cred.save":"Save","sc.cred.saving":"Saving…","sc.cred.cancel":"Cancel","sc.cred.close":"Close","sc.cred.sshDesc":"Use an SSH key for push/pull. halo runs one ssh-agent shared with its built-in terminal — unlock a key below and push/pull just works. The passphrase goes straight to ssh-add; halo never stores it.","sc.cred.sshAgentOn":"ssh-agent ready \xb7 {count} key(s) loaded","sc.cred.sshAgentOff":"ssh-agent not running","sc.cred.sshRefresh":"Refresh","sc.cred.sshKeys":"Keys in ~/.ssh","sc.cred.sshNoKeys":"No private keys found in ~/.ssh","sc.cred.sshLoaded":"Loaded","sc.cred.sshUnlock":"Unlock","sc.cred.sshUnlocking":"Unlocking…","sc.cred.sshPassphrase":"Key passphrase","sc.cred.sshUnlockHint":"Enter the passphrase to load this key into the agent. It goes straight to ssh-add — never stored or logged.","sc.cred.sshUnlockFailed":"Could not unlock the key","sc.cred.sshRemote":"Remote protocol (origin)","sc.cred.sshRestartNote":"Note: a loaded SSH key lives in the agent only while the server runs — after a halo restart, unlock it again (like re-unlocking SSH after a reboot). HTTPS tokens are stored on disk and survive restarts.","common.language":"Language","common.global":"Global","common.workspace":"Workspace","cron.title":"Cron Jobs","cron.new":"New","cron.refresh":"Refresh","cron.empty":'No cron jobs yet. Click "New" to create one.',"cron.loading":"Loading…","cron.error":"Error: {error}","cron.confirmDelete":"Delete this cron job? Run history is also removed.","cron.err.wechat.noInbound":"This user has never messaged the bot — WeChat ilink requires an inbound from them first before any push works. Ask them to send any message to the bot, then retry.","cron.next":"next:","cron.runNow":"Run now","cron.edit":"Edit","cron.delete":"Delete","cron.disabled":"disabled","cron.field.schedule":"Schedule:","cron.field.runAt":"Run at:","cron.field.agent":"Agent:","cron.field.workspace":"Workspace:","cron.field.targets":"Targets:","cron.field.targetsLogOnly":"— (log only)","cron.field.prompt":"Prompt","cron.runHistory":"Run history","cron.noRuns":"No runs yet.","cron.dispatch":"Dispatch:","cron.loadOlder":"Load older runs","cron.log.title":"Log: {id}","cron.log.close":"Close","cron.pickJob":'Pick a job, or click "New" to create one.',"cron.form.create":"Create cron job","cron.form.edit":"Edit cron job","cron.form.cancel":"Cancel","cron.form.save":"Save","cron.form.create.btn":"Create","cron.form.saving":"Saving…","cron.form.label":"Label (optional)","cron.form.label.placeholder":"Daily report","cron.form.workspace":"Workspace path *","cron.form.workspace.placeholder":"/abs/path/to/workspace","cron.form.agent":"Agent *","cron.form.mode":"Trigger","cron.form.mode.recurring":"Recurring (cron)","cron.form.mode.oneShot":"One-time (specific instant)","cron.form.schedule":"Schedule (cron expression) *","cron.form.schedule.placeholder":"0 9 * * *","cron.form.schedule.hint":"Standard 5-field cron. Examples: 0 9 * * * = daily 09:00 \xb7 */15 * * * * = every 15 min \xb7 0 9 * * 1 = Mondays 09:00","cron.form.runAt":"Fire at *","cron.form.runAt.hint":"Job fires once at this instant (interpreted in the chosen timezone), then auto-disables.","cron.form.timezone":"Timezone","cron.form.timezone.hostDefault":"Default — host TZ ({tz})","cron.form.userPrompt":"User prompt *","cron.form.userPrompt.placeholder":"Generate today's status digest from .halo/memory/","cron.form.targets":"Channel targets (where to deliver the output)","cron.form.targets.empty":"No bound channels yet.","cron.form.targets.disabled":"(disabled)","cron.form.targets.noActiveChat":"No user has messaged this account yet — cron can't deliver until at least one inbound message","cron.form.targets.noActiveChatLabel":"(no active chat)","cron.form.targets.hint":"Leave empty to log only (no channel delivery).","cron.form.enabled":"Enabled (start scheduling immediately)","cron.form.err.workspace":"Workspace path required","cron.form.err.agent":"Agent required","cron.form.err.schedule":"Schedule required","cron.form.err.runAt":"Fire time required","cron.form.err.runAtPast":"Fire time must be in the future","cron.form.err.prompt":"Prompt required","evolution.title":"Evolution","evolution.refresh":"Refresh","evolution.filter.all":"All","evolution.filter.awaiting_review":"Awaiting review","evolution.filter.pending":"Pending","evolution.filter.running":"Running","evolution.filter.approved":"Approved","evolution.filter.applied":"Applied","evolution.filter.skipped":"Skipped","evolution.filter.rejected":"Rejected","evolution.filter.failed":"Failed","evolution.filter.timeout":"Timeout","evolution.filter.archived":"Archived","evolution.list.empty":"No runs match this filter.","evolution.list.pickRun":"Select a run to view details.","evolution.list.loading":"Loading…","evolution.list.noRun":"No run.","evolution.list.noHint":"no hint","evolution.list.attempts":"{n} attempts","evolution.list.applyFailed":"apply failed","evolution.detail.workspace":"Workspace:","evolution.detail.hint":"Hint:","evolution.detail.failure":"Failure: {reason}","evolution.detail.skipped":"Evo skipped this run on its own — no patch to apply, no need to approve / reject. See .skip.md below for the reason.","evolution.detail.applyFailed":"Approved, but the apply failed its regression check and the main workspace was left unchanged. Add a hint and retry.","evolution.action.approve":"Approve","evolution.action.reject":"Reject","evolution.action.confirmReject":"Reject this run?","evolution.action.addHint":"Add hint","evolution.action.retry":"Retry","evolution.action.retryWithHint":"Retry with hint","evolution.action.confirmApprove":"Confirm approve","evolution.action.confirmRetry":"Confirm retry","evolution.action.add":"Add","evolution.action.cancel":"Cancel","evolution.action.retryNeedsHint":"Retry needs a hint — same input + no new direction would just reproduce the same output.","evolution.action.delete":"Delete","evolution.action.confirmDelete":"Delete this run? Its patch, score, logs, and snapshot are removed for good.","evolution.retry.title":"Retry hint — reviewer's steering for the next pass. Replaces the existing user hint, evo sees this as the new ask.","evolution.retry.placeholder":"e.g. last time you proposed an architecture change — try a smaller patch focused on the user pattern","evolution.retry.tooltip":"Send the run back to evo with a new hint","evolution.approve.title":"Optional reviewer hint — passed to the apply agent. Leave empty to approve as-is.","evolution.approve.placeholder":"e.g. only apply the INSTRUCTIONS.md change, not the AGENT.md one","evolution.hint.title":"Append a memo to the run's hint. Status doesn't change; this is just a note for later.","evolution.hint.placeholder":"e.g. revisit after the upcoming Bedrock model upgrade","evolution.score.title":"Score","evolution.score.lint":"lint","evolution.score.behavior":"behavior","evolution.score.scope":"scope","evolution.score.avg":"avg","evolution.score.confidence":"Confidence:","evolution.snapshot.title":"Source session ({n} msgs)","evolution.snapshot.firstUser":"First user:","evolution.snapshot.firstAssistant":"First reply:","evolution.skip.title":".skip.md (why evo skipped this run)","evolution.patch.title":"patch.md","evolution.patch.empty":"no patch produced yet","evolution.logs.title":"Run logs","evolution.logs.wrapper":"wrapper.log (orchestration)","evolution.logs.subCli":"sub-cli.log (evo / score / dry-run stdout+stderr)","evolution.time.justNow":"just now","evolution.time.minutes":"{n}m ago","evolution.time.hours":"{n}h ago","evolution.time.days":"{n}d ago","workspace.pin":"Keep window on top","workspace.unpin":"Stop keeping on top","capture.button":"Share a window/screen for the AI to see","capture.bound":"Sharing: {name} (AI can request a screenshot)","capture.unbind":"Stop sharing","capture.pickTitle":"Choose a window or screen to share","capture.permissionHint":"Screen Recording permission is needed. Enable Halo under System Settings → Privacy & Security → Screen Recording, then fully quit and reopen Halo.","capture.openSettings":"Open Settings","capture.loading":"Loading sources…","capture.empty":"No windows or screens found.","capture.noPreview":"No preview (grant Screen Recording)","capture.llmPrompt":'[System: the user is sharing the "{name}" window. When you need to see its current contents to answer, output a line containing exactly <<<CAPTURE>>> and the system will screenshot it and send it back to you as the next message. Do not output the marker unless you actually need to look.]',"capture.cameraName":"Camera","capture.cameraButton":"Turn on the camera for the AI to take a photo","capture.cameraBound":"Camera on (AI can take a photo) — click to turn off","capture.cameraPick":"Choose camera","capture.cameraCurrent":"current","capture.cameraUse":"Use this camera","capture.cameraPermissionHint":"Camera permission is needed. Enable Halo under System Settings → Privacy & Security → Camera.","capture.cameraLlmPrompt":"[System: the user has turned the camera on for you. When you need to see what the camera is pointed at to answer, output a line containing exactly <<<CAPTURE>>> and the system will take a photo and send it back to you as the next message. Do not output the marker unless you actually need to look.]","capture.dismiss":"Dismiss","face.button":"Open the assistant's face"},r={"channels.weixin":"微信","tg.title":"Telegram Bot","tg.desc":"添加 BotFather 创建的 bot,用 Telegram 对话即进入绑定的 workspace","tg.add":"添加 Bot","tg.loading":"加载中...","tg.empty":"还没有 Bot。在 Telegram 找 @BotFather 创建一个,然后点右上角添加。","tg.saveFailed":"保存失败: {error}","tg.switchFailed":"切换失败: {error}","tg.confirmDelete":"确定删除 Bot @{name}?","tg.deleteFailed":"删除失败: {error}","tg.label":"名称","tg.labelPlaceholder":"Bot 名称","tg.workspace":"绑定 workspace(绝对路径)","tg.accessLevel":"权限级别","tg.readonly":"Readonly — 仅读取(默认)","tg.wsWrite":"Workspace — workspace 内读写","tg.full":"Full — 完整权限","tg.allowedUsers":"允许的用户(逗号分隔 user_id 或 @username,留空=不限制)","tg.save":"保存","tg.cancel":"取消","tg.whitelist":"白名单","tg.pathMissing":"(路径不存在)","tg.disable":"停用","tg.enable":"启用","tg.edit":"编辑","tg.delete":"删除","tg.addTitle":"添加 Telegram Bot","tg.tokenLabel":"Bot Token(从 @BotFather 获取)","tg.bindWorkspace":"绑定到 workspace","tg.nameOptional":"名称(可选)","tg.namePlaceholder":"比如:SA Agent","tg.allowedUsersOptional":"允许的用户(可选,逗号分隔)","tg.allowedUsersHint":"user_id 或 @username,留空=不限制","tg.addBtn":"添加","slack.title":"Slack Bot","slack.desc":"添加启用了 Socket Mode 的 Slack 应用。Bot 通过长连 ws 接收消息——无需公网 webhook。","slack.add":"添加 Bot","slack.loading":"加载中...","slack.empty":"还没有 Slack Bot。在 api.slack.com 创建应用、启用 Socket Mode,然后点右上角添加。","slack.saveFailed":"保存失败:{error}","slack.switchFailed":"切换失败:{error}","slack.confirmDelete":"确定删除 team {name} 的 bot?","slack.deleteFailed":"删除失败:{error}","slack.label":"名称","slack.labelPlaceholder":"Bot 名称","slack.workspace":"绑定 workspace(绝对路径)","slack.accessLevel":"权限级别","slack.readonly":"只读 — 仅查看(默认)","slack.wsWrite":"Workspace — 工作区内可读写","slack.full":"完全 — 全部权限","slack.save":"保存","slack.cancel":"取消","slack.pathMissing":"(路径不存在)","slack.disable":"禁用","slack.enable":"启用","slack.edit":"编辑","slack.delete":"删除","slack.addTitle":"添加 Slack Bot","slack.botTokenLabel":"Bot Token(xoxb-…)","slack.botTokenHint":"OAuth & Permissions → Bot User OAuth Token","slack.appTokenLabel":"App Token(xapp-…)","slack.appTokenHint":"Basic Information → App-Level Tokens(需 connections:write 权限)","slack.bindWorkspace":"绑定 workspace","slack.nameOptional":"名称(可选)","slack.namePlaceholder":"比如 SA Agent","slack.addBtn":"添加","feishu.title":"飞书 Bot","feishu.desc":"添加启用了「长连接」事件订阅模式的飞书应用。Bot 通过 wss 长连接收消息——无需公网 webhook。","feishu.add":"添加 Bot","feishu.loading":"加载中...","feishu.empty":"还没有飞书 Bot。在飞书开放平台创建应用,启用长连接事件订阅,然后点右上角添加。","feishu.saveFailed":"保存失败:{error}","feishu.switchFailed":"切换失败:{error}","feishu.confirmDelete":"确定删除 app {name} 的 bot?","feishu.deleteFailed":"删除失败:{error}","feishu.label":"名称","feishu.labelPlaceholder":"Bot 名称","feishu.workspace":"绑定 workspace(绝对路径)","feishu.accessLevel":"权限级别","feishu.readonly":"只读 — 仅查看(默认)","feishu.wsWrite":"Workspace — 工作区内可读写","feishu.full":"完全 — 全部权限","feishu.save":"保存","feishu.cancel":"取消","feishu.pathMissing":"(路径不存在)","feishu.disable":"禁用","feishu.enable":"启用","feishu.edit":"编辑","feishu.delete":"删除","feishu.addTitle":"添加飞书 Bot","feishu.appIdLabel":"App ID","feishu.appSecretLabel":"App Secret","feishu.botOpenIdLabel":"Bot Open ID","feishu.botOpenIdHint":"机器人自己的 open_id,用于在群里识别 @ 提及","feishu.verificationTokenLabel":"Verification Token(可选)","feishu.encryptKeyLabel":"Encrypt Key(可选)","feishu.bindWorkspace":"绑定 workspace","feishu.nameOptional":"名称(可选)","feishu.namePlaceholder":"比如 SA Agent","feishu.addBtn":"添加","web.title":"Web 接入","web.desc":"创建 Token 给 Web 前端接入,每个 Token 绑定一个 workspace","web.create":"创建接入","web.loading":"加载中...","web.empty":"还没有 Web 接入。点右上角创建一个 Token。","web.saveFailed":"保存失败: {error}","web.switchFailed":"切换失败: {error}","web.confirmDelete":"确定删除此接入?Token 将失效。","web.deleteFailed":"删除失败: {error}","web.label":"名称","web.labelPlaceholder":"接入名称","web.workspace":"绑定 workspace(绝对路径)","web.accessLevel":"权限级别","web.readonly":"Readonly — 仅读取(默认)","web.wsWrite":"Workspace — workspace 内读写","web.full":"Full — 完整权限","web.observer":"Observer — 全局只读(看板/监控)","web.save":"保存","web.cancel":"取消","web.pathMissing":"(路径不存在)","web.copied":"已复制","web.copyToken":"复制 Token","web.disable":"停用","web.enable":"启用","web.edit":"编辑","web.delete":"删除","web.createSuccess":"创建成功","web.tokenNotice":"请保存以下 Token(只显示一次):","web.done":"完成","web.createTitle":"创建 Web 接入","web.bindWorkspace":"绑定到 workspace","web.nameOptional":"名称(可选)","web.namePlaceholder":"比如:SA Demo","web.createBtn":"创建","wx.title":"微信机器人","wx.desc":"扫码登录后,微信消息会进入绑定的 workspace 会话","wx.add":"添加机器人","wx.loading":"加载中...","wx.empty":'还没有机器人。点右上角"添加机器人"用微信扫码接入。',"wx.saveFailed":"保存失败: {error}","wx.switchFailed":"切换失败: {error}","wx.confirmDelete":'确定删除机器人 "{name}"?微信里的好友会变成无响应状态。',"wx.deleteFailed":"删除失败: {error}","wx.label":"名称","wx.labelPlaceholder":"Bot 名称","wx.workspace":"绑定 workspace(绝对路径)","wx.accessLevel":"权限级别","wx.readonly":"Readonly — 仅读 workspace 内文件 / grep / glob(默认)","wx.wsWrite":"Workspace — workspace 内读写","wx.full":"Full — 完整权限,含写入和 shell","wx.readonlyHint":"Readonly:微信用户只能让 agent 查文件/搜索,无法写入、跑命令或联网。子 agent 自动继承同档位","wx.save":"保存","wx.cancel":"取消","wx.disable":"停用","wx.enable":"启用","wx.rescan":"重新扫码(刷新 token)","wx.edit":"编辑","wx.delete":"删除","wx.generatingQr":"正在生成二维码...","wx.pathRequired":"workspace 必须是绝对路径","wx.qrFailed":"生成二维码失败","wx.scanPrompt":"请使用微信扫描二维码","wx.connected":"连接成功 ({id})","wx.loginFailed":"登录失败","wx.rescanBtn":"重新扫码","wx.addTitle":"添加微信机器人","wx.bindWorkspace":"绑定到 workspace","wx.bindHint":"微信消息会进入该 workspace 的会话,web 端打开能看到","wx.nameOptional":"名称(可选)","wx.namePlaceholder":"比如:工作助理","wx.readonlyFull":"Full — 和 web 端等权","wx.readonlyShareHint":"Readonly:微信用户无法写入文件、跑 shell、或联网。子 agent 自动继承同档位。给客户分享时强烈建议用 Readonly","wx.nextStep":"下一步:扫码","wx.scanTitle":"微信扫码","wx.bindTo":"绑定到","wx.restart":"重新开始","media.image":"图片","media.video":"视频","media.voice":"语音","media.file":"文件","chat.imageSent":"[已发送 {n} 张图片]","agent.agents":"Agents","agent.selectToEdit":"选择一个 Agent 进行编辑","agent.back":"返回","agent.test":"测试","agent.edit":"编辑","agent.editHint":"打开 agent.yaml + AGENT.md 编辑器","agent.saving":"保存中…","agent.loading":"加载中...","agent.mdPreviewHint":'预览(只读 — 点击上方"编辑"修改)',"agent.basic":"基本信息","agent.name":"名称","agent.description":"描述","agent.priority":"优先级","agent.model":"模型","agent.provider":"提供商","agent.modelId":"模型 ID","agent.endpoint":"端点","agent.maxTokens":"最大 Token","agent.maxTokensDefault":"默认: {max} (模型上限)","agent.maxTokensAuto":"留空使用模型默认值","agent.systemPrompt":"系统提示词","agent.systemPromptHint":"(无 AGENT.md 时的 fallback)","agent.systemPromptPlaceholder":"给这个 agent 的指令...","agent.context":"上下文","agent.team":"团队","agent.teamHint":"(选择可委派的成员 — 选中任意一个即获得会话/委派工具;不选则无法委派)","agent.loadingTools":"加载中...","agent.noSkills":"暂无可用 Skills。请先创建。","skill.skills":"Skills","skill.noSkills":"暂无 Skills。点击分组旁的 + 创建。","settings.general":"常规","settings.params":"参数","settings.emptyParams":"暂无参数。在 YAML 视图中添加 params.x.y 条目,可在 AGENT.md / SKILL.md 中通过 {{params.x.y}} 引用。","settings.emptyGeneral":"该分类暂无配置项。切换到 YAML 视图手动添加。","settings.nav.system":"系统","settings.nav.providers":"模型供应商","settings.nav.skills":"技能","settings.nav.agents":"Agent","settings.nav.orphans":"孤儿项","settings.nav.unclaimed":"无主值","settings.section.namespace":"命名空间","settings.section.declaredBy":"声明方","settings.section.declaredBy.general":"系统","settings.section.declaredBy.provider":"模型供应商","settings.section.declaredBy.skill":"技能","settings.section.declaredBy.agent":"Agent","settings.section.settings":"设置","settings.section.params":"参数","settings.section.secrets":"密钥","settings.section.secretsHint":"仅服务端可见,永不注入到 Agent 提示词或 shell 命令中。{placeholder} 占位符会在读取时从进程环境变量中替换。","settings.field.reset":"重置","settings.field.resetTitleWorkspace":"移除 workspace 覆盖(回退到 global)","settings.field.resetTitleGlobal":"移除 global 值(回到默认)","settings.source.workspace":"workspace","settings.source.global":"global","settings.source.inherited":"继承自 global","settings.source.unset":"未设置","settings.orphans.title":"无主值","settings.orphans.intro":"这些值在 settings.yaml 中存在,但当前没有任何模型供应商或技能声明它们。可能是卸载了对应包,或声明被移除。保留(如果对应命名空间回归会自动接上)或删除都安全。","settings.orphans.remove":"移除","settings.orphans.confirm":"移除孤儿项 {key}?将从 settings.yaml 删除该值。","settings.empty":"未选中分类。","settings.loading":"加载中…","nav.explorer":"文件","nav.sourceControl":"源代码管理","nav.sessions":"会话","nav.channels":"渠道","nav.settings":"设置","nav.canvas":"画布","nav.chat":"对话","nav.terminal":"终端","sc.refresh":"刷新","sc.staged":"已暂存的更改","sc.changes":"更改","sc.stageAll":"暂存所有更改","sc.unstageAll":"取消暂存所有更改","sc.commit":"提交","sc.commitPlaceholder":"消息(Ctrl+Enter 提交)","sc.push":"推送","sc.pull":"拉取","sc.clean":"没有更改","sc.initRepo":"初始化仓库","sc.initRepoDesc":"用 git 开始跟踪此文件夹。会创建 .gitignore 和一个初始提交。","sc.initializing":"初始化中…","sc.addRemoteDesc":"尚未配置远程仓库。填入你在 GitHub/GitLab 上新建的空仓库地址即可推送。","sc.remotePlaceholder":"https://github.com/user/repo.git","sc.addRemote":"添加远程仓库","sc.adding":"添加中…","sc.authError":"推送失败:需要先配置 Git 凭据。配置后再推送。","sc.openCreds":"打开凭据设置","sc.noWorkspace":"打开一个文件夹以使用源代码管理","sc.selectFile":"选择文件查看差异对比","sc.loadingDiff":"正在加载差异…","sc.diffStaged":"(已暂存)","sc.diffWorking":"(工作区)","sc.viewChanges":"更改","sc.viewGraph":"提交历史","sc.noCommits":"暂无提交","sc.noCommitFiles":"无文件变更","sc.pushed":"已推送到远程","sc.unpushed":"仅本地 — 未推送","sc.cred.title":"Git 凭据","sc.cred.tabHttps":"HTTPS","sc.cred.tabSsh":"SSH","sc.cred.httpsDesc":"用 GitHub/GitLab 用户名 + 个人访问令牌(PAT,不是账号密码)登录。凭据存在磁盘上——配一次,重启不丢。","sc.cred.configuredList":"已配置的凭据","sc.cred.noCredsYet":"还没有配置任何凭据","sc.cred.delete":"删除","sc.cred.confirmDelete":"确认删除?","sc.cred.addNew":"添加新凭据","sc.cred.host":"地址","sc.cred.username":"用户名","sc.cred.token":"个人访问令牌","sc.cred.tokenHint":"以 0600 权限保存在本地,不会再次显示。","sc.cred.tokenLink":"如何生成 PAT","sc.cred.required":"地址、用户名和令牌都必填。","sc.cred.save":"保存","sc.cred.saving":"保存中…","sc.cred.cancel":"取消","sc.cred.close":"关闭","sc.cred.sshDesc":"用 SSH 密钥推送/拉取。halo 自持一个与内置终端共享的 ssh-agent——在下面解锁密钥,推送/拉取即可生效。密码直接交给 ssh-add,halo 不会保存。","sc.cred.sshAgentOn":"ssh-agent 就绪 \xb7 已加载 {count} 个密钥","sc.cred.sshAgentOff":"ssh-agent 未运行","sc.cred.sshRefresh":"刷新","sc.cred.sshKeys":"~/.ssh 中的密钥","sc.cred.sshNoKeys":"~/.ssh 中未找到私钥","sc.cred.sshLoaded":"已加载","sc.cred.sshUnlock":"解锁","sc.cred.sshUnlocking":"解锁中…","sc.cred.sshPassphrase":"密钥密码","sc.cred.sshUnlockHint":"输入密码将此密钥加载进 agent。密码直接交给 ssh-add——不保存、不记录日志。","sc.cred.sshUnlockFailed":"无法解锁该密钥","sc.cred.sshRemote":"Remote 协议(origin)","sc.cred.sshRestartNote":"注意:加载的 SSH 密钥只在服务运行期间存在于 agent 中——halo 重启后需要重新解锁一次(类似电脑重启后要重新解锁 SSH)。HTTPS 令牌存在磁盘上,重启不丢。","common.language":"语言","common.global":"全局","common.workspace":"工作区","cron.title":"定时任务","cron.new":"新建","cron.refresh":"刷新","cron.empty":"还没有定时任务。点击「新建」创建一个。","cron.loading":"加载中…","cron.error":"错误:{error}","cron.confirmDelete":"删除这个定时任务?运行历史也会一起删掉。","cron.err.wechat.noInbound":"该用户尚未给此 bot 发过消息——微信 ilink 协议要求对方先开口,bot 才能主动推送。请让 ta 给 bot 发任意一条消息后再试。","cron.next":"下一次:","cron.runNow":"立即运行","cron.edit":"编辑","cron.delete":"删除","cron.disabled":"已禁用","cron.field.schedule":"调度:","cron.field.runAt":"触发时刻:","cron.field.agent":"Agent:","cron.field.workspace":"工作区:","cron.field.targets":"通知目标:","cron.field.targetsLogOnly":"— (仅记录日志)","cron.field.prompt":"提示词","cron.runHistory":"运行历史","cron.noRuns":"还没有运行记录。","cron.dispatch":"通知:","cron.loadOlder":"加载更早的运行","cron.log.title":"日志:{id}","cron.log.close":"关闭","cron.pickJob":"挑一个任务,或点「新建」创建一个。","cron.form.create":"新建定时任务","cron.form.edit":"编辑定时任务","cron.form.cancel":"取消","cron.form.save":"保存","cron.form.create.btn":"创建","cron.form.saving":"保存中…","cron.form.label":"标签(可选)","cron.form.label.placeholder":"每日报告","cron.form.workspace":"工作区路径 *","cron.form.workspace.placeholder":"/绝对路径/到/workspace","cron.form.agent":"Agent *","cron.form.mode":"触发方式","cron.form.mode.recurring":"周期性(cron 表达式)","cron.form.mode.oneShot":"一次性(指定时刻触发)","cron.form.schedule":"调度(cron 表达式)*","cron.form.schedule.placeholder":"0 9 * * *","cron.form.schedule.hint":"标准 5 段 cron。例:0 9 * * * = 每天 09:00 \xb7 */15 * * * * = 每 15 分钟 \xb7 0 9 * * 1 = 每周一 09:00","cron.form.runAt":"触发时刻 *","cron.form.runAt.hint":"任务会在选中的时区下,按这个时刻触发一次,触发后自动禁用。","cron.form.timezone":"时区","cron.form.timezone.hostDefault":"默认 — 主机时区({tz})","cron.form.userPrompt":"用户提示词 *","cron.form.userPrompt.placeholder":"从 .halo/memory/ 整理今天的状态摘要","cron.form.targets":"通知目标(最终输出发到哪里)","cron.form.targets.empty":"还没有绑定的渠道。","cron.form.targets.disabled":"(已禁用)","cron.form.targets.noActiveChat":"还没有用户跟这个账号说过话 —— 在收到至少一条消息之前,cron 没法发送。","cron.form.targets.noActiveChatLabel":"(没有活跃会话)","cron.form.targets.hint":"留空表示仅记录日志(不通过渠道发送)。","cron.form.enabled":"启用(创建后立刻开始排期)","cron.form.err.workspace":"需要工作区路径","cron.form.err.agent":"需要选择 Agent","cron.form.err.schedule":"需要 cron 表达式","cron.form.err.runAt":"需要触发时刻","cron.form.err.runAtPast":"触发时刻必须在未来","cron.form.err.prompt":"需要填写提示词","evolution.title":"自我进化","evolution.refresh":"刷新","evolution.filter.all":"全部","evolution.filter.awaiting_review":"待审","evolution.filter.pending":"待跑","evolution.filter.running":"运行中","evolution.filter.approved":"已批准","evolution.filter.applied":"已应用","evolution.filter.skipped":"已跳过","evolution.filter.rejected":"已拒绝","evolution.filter.failed":"失败","evolution.filter.timeout":"超时","evolution.filter.archived":"已归档","evolution.list.empty":"当前筛选下没有运行记录。","evolution.list.pickRun":"选一个运行查看详情。","evolution.list.loading":"加载中…","evolution.list.noRun":"没有数据。","evolution.list.noHint":"没有提示","evolution.list.attempts":"{n} 次尝试","evolution.list.applyFailed":"应用失败","evolution.detail.workspace":"工作区:","evolution.detail.hint":"提示:","evolution.detail.failure":"失败:{reason}","evolution.detail.skipped":"Evo 主动跳过了这次 run —— 没有 patch 需要应用,无需 approve / reject。原因见下方 .skip.md。","evolution.detail.applyFailed":"已批准,但应用时未通过回归校验,主工作区未改动。补充提示后可重试。","evolution.action.approve":"批准","evolution.action.reject":"拒绝","evolution.action.confirmReject":"确定拒绝这次 run?","evolution.action.addHint":"添加备注","evolution.action.retry":"重试","evolution.action.retryWithHint":"带提示重试","evolution.action.confirmApprove":"确认批准","evolution.action.confirmRetry":"确认重试","evolution.action.add":"添加","evolution.action.cancel":"取消","evolution.action.retryNeedsHint":"重试需要带提示 —— 相同输入没有新指令,只会跑出同样的结果。","evolution.action.delete":"删除","evolution.action.confirmDelete":"删除这次 run?它的 patch、评分、日志和快照会被一并永久删除。","evolution.retry.title":"重试提示 —— 给 evo 的新指引,会替换原来的用户提示。","evolution.retry.placeholder":"例如:上次提了架构层面的改动,这次试试更小的、聚焦在用户实际模式上的 patch","evolution.retry.tooltip":"把这次 run 带新提示发回 evo","evolution.approve.title":"可选的审核提示 —— 会传给 apply agent。留空就按 patch 原样应用。","evolution.approve.placeholder":"例如:只应用 INSTRUCTIONS.md 那部分,不要碰 AGENT.md","evolution.hint.title":"给这次 run 加一条备忘。状态不变,仅作为后续查阅的笔记。","evolution.hint.placeholder":"例如:等下次 Bedrock 模型升级之后再回来看","evolution.score.title":"评分","evolution.score.lint":"lint","evolution.score.behavior":"行为","evolution.score.scope":"范围","evolution.score.avg":"平均","evolution.score.confidence":"置信度:","evolution.snapshot.title":"源会话({n} 条消息)","evolution.snapshot.firstUser":"首条用户消息:","evolution.snapshot.firstAssistant":"首条回复:","evolution.skip.title":".skip.md(evo 跳过本次 run 的理由)","evolution.patch.title":"patch.md","evolution.patch.empty":"还没有产出 patch","evolution.logs.title":"运行日志","evolution.logs.wrapper":"wrapper.log(编排层)","evolution.logs.subCli":"sub-cli.log(evo / score / dry-run 的标准输出)","evolution.time.justNow":"刚刚","evolution.time.minutes":"{n} 分钟前","evolution.time.hours":"{n} 小时前","evolution.time.days":"{n} 天前","workspace.pin":"窗口置顶","workspace.unpin":"取消置顶","capture.button":"共享一个窗口/屏幕给 AI 查看","capture.bound":"正在共享:{name}(AI 可主动截图查看)","capture.unbind":"停止共享","capture.pickTitle":"选择要共享的窗口或屏幕","capture.permissionHint":"需要「屏幕录制」权限。请在 系统设置 → 隐私与安全性 → 屏幕录制 里勾选 Halo,然后彻底退出并重新打开 Halo。","capture.openSettings":"打开系统设置","capture.loading":"正在加载来源…","capture.empty":"未找到窗口或屏幕。","capture.noPreview":"无预览(请授予屏幕录制权限)","capture.llmPrompt":"[系统:用户正在共享「{name}」窗口。当你需要查看它的当前画面来回答时,单独输出一行 <<<CAPTURE>>>,系统会自动截图并作为下一条消息发给你。不需要查看时不要输出该标记。]","capture.cameraName":"摄像头","capture.cameraButton":"开启摄像头,让 AI 主动拍照","capture.cameraBound":"摄像头已开启(AI 可主动拍照)— 点击关闭","capture.cameraPick":"选择摄像头","capture.cameraCurrent":"当前","capture.cameraUse":"使用此摄像头","capture.cameraPermissionHint":"需要「摄像头」权限。请在 系统设置 → 隐私与安全性 → 摄像头 里勾选 Halo。","capture.cameraLlmPrompt":"[系统:用户已为你开启摄像头。当你需要查看摄像头当前画面来回答时,单独输出一行 <<<CAPTURE>>>,系统会自动拍一张照片并作为下一条消息发给你。不需要查看时不要输出该标记。]","capture.dismiss":"关闭","face.button":"打开 AI 的面孔"};function i(e){let{children:t}=e;return(0,n.jsx)(a.I18nProvider,{en:s,zh:r,children:t})}},38045:(e,t,o)=>{o.d(t,{LangSwitcher:()=>r});var n=o(69625),a=o(8808);let s=[{value:"en",label:"EN"},{value:"zh",label:"中"}];function r(e){let{className:t}=e,{lang:o,setLang:r}=(0,a.useI18n)();return(0,n.jsx)("select",{value:o,onChange:e=>r(e.target.value),className:"px-1 py-0.5 text-xs rounded border border-[var(--border)] bg-transparent text-[var(--muted-foreground)] hover:text-[var(--foreground)] hover:border-[var(--foreground)] transition-colors cursor-pointer ".concat(null!=t?t:""),children:s.map(e=>(0,n.jsx)("option",{value:e.value,children:e.label},e.value))})}},87826:(e,t,o)=>{o.d(t,{LanguageSelect:()=>s});var n=o(69625),a=o(8808);function s(e){let{value:t,onChange:o}=e,s=(0,a.useT)();return(0,n.jsxs)("div",{children:[(0,n.jsx)("label",{className:"text-[10px] text-[var(--muted-foreground)]",children:s("common.language")}),(0,n.jsxs)("select",{value:t,onChange:e=>o(e.target.value),className:"mt-0.5 w-full rounded border border-[var(--border)] bg-[var(--card)] px-2 py-1 text-xs",children:[(0,n.jsx)("option",{value:"en",children:"English"}),(0,n.jsx)("option",{value:"zh",children:"中文"})]})]})}}}]);
@@ -1 +0,0 @@
1
- "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[661],{4476:(e,r,t)=>{t.d(r,{I:()=>i,n:()=>l});var n=t(69625),a=t(29412),s=t(17612);function l(e){let{name:r,downloadUrl:t,onOpenAsText:l,extraToolbar:o,loading:c,error:d,children:u}=e;return(0,n.jsxs)("div",{className:"flex h-full flex-col bg-[var(--background)]",children:[(0,n.jsxs)("div",{className:"flex h-8 shrink-0 items-center gap-2 border-b border-[var(--border)] bg-[var(--card)] px-3",children:[(0,n.jsx)("span",{className:"truncate text-xs text-[var(--muted-foreground)]",children:r}),(0,n.jsx)("div",{className:"flex-1"}),o,l&&(0,n.jsxs)(i,{onClick:l,title:"Open as text",children:[(0,n.jsx)(a.A,{className:"h-3 w-3"}),(0,n.jsx)("span",{children:"Open as Text"})]}),(0,n.jsxs)("a",{href:t,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:"Download",children:[(0,n.jsx)(s.A,{className:"h-3 w-3"}),(0,n.jsx)("span",{children:"Download"})]})]}),(0,n.jsx)("div",{className:"relative min-h-0 flex-1",children:d?(0,n.jsx)("div",{className:"flex h-full items-center justify-center text-sm text-red-400",children:d}):(0,n.jsxs)(n.Fragment,{children:[u,c&&(0,n.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 i(e){let{onClick:r,title:t,children:a}=e;return(0,n.jsx)("button",{onClick:r,title:t,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:a})}},4661:(e,r,t)=>{t.r(r),t.d(r,{PptxPreview:()=>i});var n=t(69625),a=t(43853),s=t(44024),l=t(4476);function i(e){let{name:r,viewUrl:t,downloadUrl:i,onOpenAsText:o}=e,c=(0,a.useRef)(null),[d,u]=(0,a.useState)(null),[x,f]=(0,a.useState)(!0);return(0,a.useEffect)(()=>{let e=c.current;if(!e)return;let r=!1,n=new AbortController,a=null;return async function(){try{let l=await fetch(t,{signal:n.signal}),i=await l.arrayBuffer();if(r||!e||(await new Promise(e=>setTimeout(e,0)),r||!e))return;e.innerHTML="",a=(0,s.T)(e,{width:e.clientWidth||960,height:e.clientHeight||720,mode:"list"}),await a.preview(i),r||f(!1)}catch(e){if(r||e instanceof DOMException&&"AbortError"===e.name)return;u(e instanceof Error?e.message:"Failed to load presentation")}}(),()=>{r=!0,n.abort();try{null==a||a.destroy()}catch(e){}}},[t]),(0,n.jsx)(l.n,{name:r,downloadUrl:i,onOpenAsText:o,loading:x,error:d,children:(0,n.jsx)("div",{className:"h-full overflow-auto bg-zinc-100",children:(0,n.jsx)("div",{ref:c,className:"min-h-full w-full"})})})}}}]);
@@ -1 +0,0 @@
1
- (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[177],{25755:()=>{},43764:(e,s,n)=>{Promise.resolve().then(n.t.bind(n,61983,23)),Promise.resolve().then(n.t.bind(n,25755,23)),Promise.resolve().then(n.bind(n,8808)),Promise.resolve().then(n.bind(n,38045)),Promise.resolve().then(n.bind(n,87826)),Promise.resolve().then(n.bind(n,26967))},61983:e=>{e.exports={style:{fontFamily:"'Inter', 'Inter Fallback'",fontStyle:"normal"},className:"__className_f367f3",variable:"__variable_f367f3"}}},e=>{e.O(0,[578,298,489,635,358],()=>e(e.s=43764)),_N_E=e.O()}]);