@turmind/halo 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +143 -0
- package/admin-out/404.html +1 -0
- package/admin-out/_next/static/IBP_mwBDf-cPwZSZZGpi8/_buildManifest.js +1 -0
- package/admin-out/_next/static/IBP_mwBDf-cPwZSZZGpi8/_ssgManifest.js +1 -0
- package/admin-out/_next/static/chunks/1d2d5650-810a3ca1ae20cab5.js +18 -0
- package/admin-out/_next/static/chunks/200.3b8f8a74f005a134.js +1 -0
- package/admin-out/_next/static/chunks/220.3dc41cd6269b88e6.js +1 -0
- package/admin-out/_next/static/chunks/298-d6b224b569da4de0.js +1 -0
- package/admin-out/_next/static/chunks/304eda7d.b1ee7f523653e027.js +1 -0
- package/admin-out/_next/static/chunks/330.3b784cf0dcd69163.js +1 -0
- package/admin-out/_next/static/chunks/442.f4302087bddb1ec1.js +1 -0
- package/admin-out/_next/static/chunks/443.d1b289781793f8de.js +25 -0
- package/admin-out/_next/static/chunks/503-7c6d9935df6e0cea.js +1 -0
- package/admin-out/_next/static/chunks/533.5f2d4bfe3d076095.js +1 -0
- package/admin-out/_next/static/chunks/579.a2718ddd395a2f3a.js +1 -0
- package/admin-out/_next/static/chunks/635-3dd0043ed0520b9d.js +1 -0
- package/admin-out/_next/static/chunks/65.859665ba6717967a.js +1 -0
- package/admin-out/_next/static/chunks/661.a4fe93f3f2a5e871.js +1 -0
- package/admin-out/_next/static/chunks/688.4e014ac6cfc7e152.js +1 -0
- package/admin-out/_next/static/chunks/868.0c95dfe283400a00.js +1 -0
- package/admin-out/_next/static/chunks/app/_not-found/page-8e35399c8fd5016d.js +1 -0
- package/admin-out/_next/static/chunks/app/layout-58f773336e6fce61.js +1 -0
- package/admin-out/_next/static/chunks/app/page-cc8043335282d745.js +1 -0
- package/admin-out/_next/static/chunks/db534642.09d3c650ff587175.js +1 -0
- package/admin-out/_next/static/chunks/fb4ec4d6-3fa44f69484f91b9.js +1 -0
- package/admin-out/_next/static/chunks/framework-c3031ae54a63fe16.js +1 -0
- package/admin-out/_next/static/chunks/main-14b48e3c2588354f.js +1 -0
- package/admin-out/_next/static/chunks/main-app-483b436873410ddc.js +1 -0
- package/admin-out/_next/static/chunks/pages/_app-b2fc6e5f920d1b1a.js +1 -0
- package/admin-out/_next/static/chunks/pages/_error-f3696a8f3d67d150.js +1 -0
- package/admin-out/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- package/admin-out/_next/static/chunks/webpack-8bd7ef88a6527feb.js +1 -0
- package/admin-out/_next/static/css/659eccb5db697b76.css +1 -0
- package/admin-out/_next/static/css/8fce4e1a54dac12a.css +3 -0
- package/admin-out/_next/static/media/19cfc7226ec3afaa-s.woff2 +0 -0
- package/admin-out/_next/static/media/21350d82a1f187e9-s.woff2 +0 -0
- package/admin-out/_next/static/media/8e9860b6e62d6359-s.woff2 +0 -0
- package/admin-out/_next/static/media/ba9851c3c22cd980-s.woff2 +0 -0
- package/admin-out/_next/static/media/c5fe6dc8356a8c31-s.woff2 +0 -0
- package/admin-out/_next/static/media/df0a9ae256c0569c-s.woff2 +0 -0
- package/admin-out/_next/static/media/e4af272ccee01ff0-s.p.woff2 +0 -0
- package/admin-out/favicon.ico +0 -0
- package/admin-out/icon.svg +18 -0
- package/admin-out/index.html +1 -0
- package/admin-out/index.txt +23 -0
- package/admin-out/monaco/vs/_commonjsHelpers-CT9FvmAN.js +1 -0
- package/admin-out/monaco/vs/abap-D-t0cyap.js +1 -0
- package/admin-out/monaco/vs/apex-CcIm7xu6.js +1 -0
- package/admin-out/monaco/vs/assets/css.worker-HnVq6Ewq.js +93 -0
- package/admin-out/monaco/vs/assets/editor.worker-Be8ye1pW.js +26 -0
- package/admin-out/monaco/vs/assets/html.worker-B51mlPHg.js +470 -0
- package/admin-out/monaco/vs/assets/json.worker-DKiEKt88.js +58 -0
- package/admin-out/monaco/vs/assets/ts.worker-CMbG-7ft.js +67731 -0
- package/admin-out/monaco/vs/azcli-BA0tQDCg.js +1 -0
- package/admin-out/monaco/vs/basic-languages/monaco.contribution.js +1 -0
- package/admin-out/monaco/vs/bat-C397hTD6.js +1 -0
- package/admin-out/monaco/vs/bicep-DF5aW17k.js +2 -0
- package/admin-out/monaco/vs/cameligo-plsz8qhj.js +1 -0
- package/admin-out/monaco/vs/clojure-Y2auQMzK.js +1 -0
- package/admin-out/monaco/vs/coffee-Bu45yuWE.js +1 -0
- package/admin-out/monaco/vs/cpp-CkKPQIni.js +1 -0
- package/admin-out/monaco/vs/csharp-CX28MZyh.js +1 -0
- package/admin-out/monaco/vs/csp-D8uWnyxW.js +1 -0
- package/admin-out/monaco/vs/css-CaeNmE3S.js +3 -0
- package/admin-out/monaco/vs/cssMode-CjiAH6dQ.js +1 -0
- package/admin-out/monaco/vs/cypher-DVThT8BS.js +1 -0
- package/admin-out/monaco/vs/dart-CmGfCvrO.js +1 -0
- package/admin-out/monaco/vs/dockerfile-CZqqYdch.js +1 -0
- package/admin-out/monaco/vs/ecl-30fUercY.js +1 -0
- package/admin-out/monaco/vs/editor/editor.main.css +1 -0
- package/admin-out/monaco/vs/editor/editor.main.js +5 -0
- package/admin-out/monaco/vs/editor.api-CalNCsUg.js +903 -0
- package/admin-out/monaco/vs/elixir-xjPaIfzF.js +1 -0
- package/admin-out/monaco/vs/flow9-DqtmStfK.js +1 -0
- package/admin-out/monaco/vs/freemarker2-Cz_sV6Md.js +3 -0
- package/admin-out/monaco/vs/fsharp-BOMdg4U1.js +1 -0
- package/admin-out/monaco/vs/go-D_hbi-Jt.js +1 -0
- package/admin-out/monaco/vs/graphql-CKUU4kLG.js +1 -0
- package/admin-out/monaco/vs/handlebars-OwglfO-1.js +1 -0
- package/admin-out/monaco/vs/hcl-DTaboeZW.js +1 -0
- package/admin-out/monaco/vs/html-Pa1xEWsY.js +1 -0
- package/admin-out/monaco/vs/htmlMode-Bz67EXwp.js +1 -0
- package/admin-out/monaco/vs/ini-CsNwO04R.js +1 -0
- package/admin-out/monaco/vs/java-CI4ZMsH9.js +1 -0
- package/admin-out/monaco/vs/javascript-PczUCGdz.js +1 -0
- package/admin-out/monaco/vs/jsonMode-DULH5oaX.js +7 -0
- package/admin-out/monaco/vs/julia-BwzEvaQw.js +1 -0
- package/admin-out/monaco/vs/kotlin-IUYPiTV8.js +1 -0
- package/admin-out/monaco/vs/language/css/monaco.contribution.js +1 -0
- package/admin-out/monaco/vs/language/html/monaco.contribution.js +1 -0
- package/admin-out/monaco/vs/language/json/monaco.contribution.js +1 -0
- package/admin-out/monaco/vs/language/typescript/monaco.contribution.js +1 -0
- package/admin-out/monaco/vs/less-C0eDYdqa.js +2 -0
- package/admin-out/monaco/vs/lexon-iON-Kj97.js +1 -0
- package/admin-out/monaco/vs/liquid-DqKjdPGy.js +1 -0
- package/admin-out/monaco/vs/loader.js +1368 -0
- package/admin-out/monaco/vs/lspLanguageFeatures-kM9O9rjY.js +4 -0
- package/admin-out/monaco/vs/lua-DtygF91M.js +1 -0
- package/admin-out/monaco/vs/m3-CsR4AuFi.js +1 -0
- package/admin-out/monaco/vs/markdown-C_rD0bIw.js +1 -0
- package/admin-out/monaco/vs/mdx-DEWtB1K5.js +1 -0
- package/admin-out/monaco/vs/mips-CiYP61RB.js +1 -0
- package/admin-out/monaco/vs/monaco.contribution-D2OdxNBt.js +1 -0
- package/admin-out/monaco/vs/monaco.contribution-DO3azKX8.js +1 -0
- package/admin-out/monaco/vs/monaco.contribution-EcChJV6a.js +1 -0
- package/admin-out/monaco/vs/monaco.contribution-qLAYrEOP.js +1 -0
- package/admin-out/monaco/vs/msdax-C38-sJlp.js +1 -0
- package/admin-out/monaco/vs/mysql-CdtbpvbG.js +1 -0
- package/admin-out/monaco/vs/nls.messages-loader.js +1 -0
- package/admin-out/monaco/vs/nls.messages.cs.js.js +17 -0
- package/admin-out/monaco/vs/nls.messages.de.js.js +17 -0
- package/admin-out/monaco/vs/nls.messages.es.js.js +17 -0
- package/admin-out/monaco/vs/nls.messages.fr.js.js +15 -0
- package/admin-out/monaco/vs/nls.messages.it.js.js +15 -0
- package/admin-out/monaco/vs/nls.messages.ja.js.js +17 -0
- package/admin-out/monaco/vs/nls.messages.js.js +10 -0
- package/admin-out/monaco/vs/nls.messages.ko.js.js +25 -0
- package/admin-out/monaco/vs/nls.messages.pl.js.js +17 -0
- package/admin-out/monaco/vs/nls.messages.pt-br.js.js +6 -0
- package/admin-out/monaco/vs/nls.messages.ru.js.js +17 -0
- package/admin-out/monaco/vs/nls.messages.tr.js.js +15 -0
- package/admin-out/monaco/vs/nls.messages.zh-cn.js.js +17 -0
- package/admin-out/monaco/vs/nls.messages.zh-tw.js.js +15 -0
- package/admin-out/monaco/vs/objective-c-CntZFaHX.js +1 -0
- package/admin-out/monaco/vs/pascal-r6kuqfl_.js +1 -0
- package/admin-out/monaco/vs/pascaligo-BiXoTmXh.js +1 -0
- package/admin-out/monaco/vs/perl-DABw_TcH.js +1 -0
- package/admin-out/monaco/vs/pgsql-me_jFXeX.js +1 -0
- package/admin-out/monaco/vs/php-D_kh-9LK.js +1 -0
- package/admin-out/monaco/vs/pla-VfZjczW0.js +1 -0
- package/admin-out/monaco/vs/postiats-BBSzz8Pk.js +1 -0
- package/admin-out/monaco/vs/powerquery-Dt-g_2cc.js +1 -0
- package/admin-out/monaco/vs/powershell-B-7ap1zc.js +1 -0
- package/admin-out/monaco/vs/protobuf-BmtuEB1A.js +2 -0
- package/admin-out/monaco/vs/pug-BRpRNeEb.js +1 -0
- package/admin-out/monaco/vs/python-Cr0UkIbn.js +1 -0
- package/admin-out/monaco/vs/qsharp-BzsFaUU9.js +1 -0
- package/admin-out/monaco/vs/r-f8dDdrp4.js +1 -0
- package/admin-out/monaco/vs/razor-BYAHOTkz.js +1 -0
- package/admin-out/monaco/vs/redis-fvZQY4PI.js +1 -0
- package/admin-out/monaco/vs/redshift-45Et0LQi.js +1 -0
- package/admin-out/monaco/vs/restructuredtext-C7UUFKFD.js +1 -0
- package/admin-out/monaco/vs/ruby-CZO8zYTz.js +1 -0
- package/admin-out/monaco/vs/rust-Bfetafyc.js +1 -0
- package/admin-out/monaco/vs/sb-3GYllVck.js +1 -0
- package/admin-out/monaco/vs/scala-foMgrKo1.js +1 -0
- package/admin-out/monaco/vs/scheme-CHdMtr7p.js +1 -0
- package/admin-out/monaco/vs/scss-C1cmLt9V.js +3 -0
- package/admin-out/monaco/vs/shell-ClXCKCEW.js +1 -0
- package/admin-out/monaco/vs/solidity-MZ6ExpPy.js +1 -0
- package/admin-out/monaco/vs/sophia-DWkuSsPQ.js +1 -0
- package/admin-out/monaco/vs/sparql-AUGFYSyk.js +1 -0
- package/admin-out/monaco/vs/sql-32GpJSV2.js +1 -0
- package/admin-out/monaco/vs/st-CuDFIVZ_.js +1 -0
- package/admin-out/monaco/vs/swift-n-2HociN.js +3 -0
- package/admin-out/monaco/vs/systemverilog-Ch4vA8Yt.js +1 -0
- package/admin-out/monaco/vs/tcl-D74tq1nH.js +1 -0
- package/admin-out/monaco/vs/tsMode-CZz1Umrk.js +11 -0
- package/admin-out/monaco/vs/twig-C6taOxMV.js +1 -0
- package/admin-out/monaco/vs/typescript-DfOrAzoV.js +1 -0
- package/admin-out/monaco/vs/typespec-D-PIh9Xw.js +1 -0
- package/admin-out/monaco/vs/vb-Dyb2648j.js +1 -0
- package/admin-out/monaco/vs/wgsl-BhLXMOR0.js +298 -0
- package/admin-out/monaco/vs/workers-DcJshg-q.js +1 -0
- package/admin-out/monaco/vs/xml-CdsdnY8S.js +1 -0
- package/admin-out/monaco/vs/yaml-DYGvmE88.js +1 -0
- package/bin/halo.js +9 -0
- package/bundled-docs/design/agent.md +145 -0
- package/bundled-docs/design/architecture.md +270 -0
- package/bundled-docs/design/background-dispatch.md +151 -0
- package/bundled-docs/design/command.md +58 -0
- package/bundled-docs/design/cron.md +88 -0
- package/bundled-docs/design/evolution.md +383 -0
- package/bundled-docs/design/express-self.md +145 -0
- package/bundled-docs/design/feishu.md +224 -0
- package/bundled-docs/design/halo-city.md +112 -0
- package/bundled-docs/design/prompt-system.md +411 -0
- package/bundled-docs/design/session.md +321 -0
- package/bundled-docs/design/slack.md +212 -0
- package/bundled-docs/design/storage.md +578 -0
- package/bundled-docs/design/telegram.md +161 -0
- package/bundled-docs/design/web.md +222 -0
- package/bundled-docs/design/wechat.md +146 -0
- package/bundled-docs/design/ws.md +144 -0
- package/bundled-docs/dev/acp-adapter.md +337 -0
- package/bundled-docs/dev/add-channel.md +376 -0
- package/bundled-docs/dev/add-model-provider.md +386 -0
- package/bundled-docs/dev/api.md +686 -0
- package/bundled-docs/dev/deploy.md +198 -0
- package/bundled-docs/dev/desktop-packaging.md +327 -0
- package/bundled-docs/dev/env.md +158 -0
- package/bundled-docs/dev/logging.md +85 -0
- package/bundled-docs/dev/previews.md +225 -0
- package/bundled-docs/dev/tools.md +331 -0
- package/bundled-docs/guide/README.md +29 -0
- package/bundled-docs/guide/agents.md +264 -0
- package/bundled-docs/guide/channels/README.md +62 -0
- package/bundled-docs/guide/channels/acp.md +150 -0
- package/bundled-docs/guide/channels/feishu.md +154 -0
- package/bundled-docs/guide/channels/slack.md +156 -0
- package/bundled-docs/guide/channels/telegram.md +112 -0
- package/bundled-docs/guide/channels/web.md +149 -0
- package/bundled-docs/guide/channels/wechat.md +110 -0
- package/bundled-docs/guide/chat.md +112 -0
- package/bundled-docs/guide/cli.md +197 -0
- package/bundled-docs/guide/getting-started.md +93 -0
- package/bundled-docs/guide/secrets-and-credentials.md +161 -0
- package/bundled-docs/guide/sessions.md +104 -0
- package/bundled-docs/guide/skills.md +302 -0
- package/bundled-docs/guide/testing-agents-and-skills.md +136 -0
- package/bundled-docs/guide/workspace.md +131 -0
- package/bundled-docs/plans/cron-tasks.md +362 -0
- package/bundled-docs/plans/design-suggestions.md +203 -0
- package/bundled-docs/plans/issue-session-deep-nesting.md +208 -0
- package/bundled-docs/plans/launch-plan.md +111 -0
- package/bundled-docs/plans/md-and-agent-refactor.md +197 -0
- package/bundled-docs/plans/remote-mac-worker.md +196 -0
- package/bundled-docs/plans/roadmap.md +40 -0
- package/bundled-docs/plans/self-evolution.md +1379 -0
- package/bundled-docs/plans/todo.md +32 -0
- package/bundled-docs/plans/weixin-channel.md +255 -0
- package/bundled-docs/requirements/agent.md +92 -0
- package/bundled-docs/requirements/chat.md +81 -0
- package/bundled-docs/requirements/command.md +130 -0
- package/bundled-docs/requirements/editor.md +82 -0
- package/bundled-docs/requirements/explorer.md +89 -0
- package/bundled-docs/requirements/overview.md +62 -0
- package/bundled-docs/requirements/session.md +73 -0
- package/bundled-docs/requirements/settings.md +224 -0
- package/bundled-docs/requirements/skill.md +125 -0
- package/bundled-docs/requirements/source-control.md +82 -0
- package/bundled-docs/requirements/terminal.md +118 -0
- package/bundled-docs/requirements/workspace.md +80 -0
- package/bundled-docs/test/docs-seeding.md +101 -0
- package/bundled-docs/test/session.md +374 -0
- package/dist/index.js +26750 -0
- package/package.json +67 -0
- package/templates/INSTRUCTIONS.md +25 -0
- package/templates/agents/__apply_agent__/AGENT.md +229 -0
- package/templates/agents/__apply_agent__/agent.yaml +30 -0
- package/templates/agents/__evo_agent__/AGENT.md +323 -0
- package/templates/agents/__evo_agent__/agent.yaml +29 -0
- package/templates/agents/__score__/AGENT.md +177 -0
- package/templates/agents/__score__/agent.yaml +29 -0
- package/templates/agents/deep-executor/AGENT.md +60 -0
- package/templates/agents/deep-executor/agent.yaml +25 -0
- package/templates/agents/default/AGENT.md +38 -0
- package/templates/agents/default/agent.yaml +51 -0
- package/templates/agents/executor/AGENT.md +38 -0
- package/templates/agents/executor/agent.yaml +25 -0
- package/templates/aliases.yaml +26 -0
- package/templates/canvas/self.html +660 -0
- package/templates/config.yaml +69 -0
- package/templates/models/anthropic.yaml +47 -0
- package/templates/models/aws-bedrock-claude-invoke.yaml +156 -0
- package/templates/models/aws-bedrock-mantle.yaml +89 -0
- package/templates/models/deepseek.yaml +62 -0
- package/templates/models/doubao.yaml +137 -0
- package/templates/models/hunyuan.yaml +56 -0
- package/templates/models/kimi.yaml +41 -0
- package/templates/models/mimo-token-plan-china.yaml +61 -0
- package/templates/models/minimax.yaml +80 -0
- package/templates/models/openai.yaml +51 -0
- package/templates/models/qwen.yaml +78 -0
- package/templates/optional-skills/tavily-web-search/SKILL.md +91 -0
- package/templates/optional-skills/tavily-web-search/config.yaml +10 -0
- package/templates/prompts/all/TOOL_GUIDELINES.md +35 -0
- package/templates/prompts/all/TOOL_SHELL.md +4 -0
- package/templates/prompts/all/TOOL_SHELL.windows.md +7 -0
- package/templates/prompts/bootstrap/BOOTSTRAP.md +26 -0
- package/templates/prompts/root/WORKSPACE_MEMORY.md +11 -0
- package/templates/schema.sql +30 -0
- package/templates/skills/acp/SKILL.md +236 -0
- package/templates/skills/acp/templates/SKILL.md.tmpl +74 -0
- package/templates/skills/acp/templates/ask.py +176 -0
- package/templates/skills/acp/templates/config.yaml.tmpl +33 -0
- package/templates/skills/agent/SKILL.md +128 -0
- package/templates/skills/aws-knowledge/SKILL.md +82 -0
- package/templates/skills/aws-knowledge/scripts/aws_kb.py +61 -0
- package/templates/skills/cron/SKILL.md +217 -0
- package/templates/skills/cron/manage-cron.py +434 -0
- package/templates/skills/halo/SKILL.md +98 -0
- package/templates/skills/nova-web-search/SKILL.md +65 -0
- package/templates/skills/self/SKILL.md +139 -0
- package/templates/skills/send-file/SKILL.md +28 -0
- package/templates/skills/send-file/feishu.md +16 -0
- package/templates/skills/send-file/slack.md +7 -0
- package/templates/skills/send-file/telegram.md +9 -0
- package/templates/skills/send-file/web.md +6 -0
- package/templates/skills/send-file/wechat.md +8 -0
- package/templates/skills/skill/SKILL.md +131 -0
- package/templates/skills/workspace/SKILL.md +28 -0
- package/templates/skills/workspace/setup-tidy.md +265 -0
- package/templates/skills/workspace/share.md +187 -0
- package/templates/skills/workspace/stage.py +487 -0
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
manage-cron — CRUD + listing for the global cron-jobs db.
|
|
4
|
+
|
|
5
|
+
Skill helper: agents call this rather than handcrafting sqlite3 queries.
|
|
6
|
+
The script handles id generation, JSON encoding for `targets`, schedule
|
|
7
|
+
validation hint (we leave the actual cron parse to the runner), and
|
|
8
|
+
keeps `updated_at` fresh.
|
|
9
|
+
|
|
10
|
+
Server's cron daemon polls cron_jobs every 10 seconds, so any change
|
|
11
|
+
made here goes live within ~10s without a server restart.
|
|
12
|
+
|
|
13
|
+
Output is always JSON on stdout for easy parsing. Errors go to stderr
|
|
14
|
+
with non-zero exit.
|
|
15
|
+
|
|
16
|
+
Subcommands:
|
|
17
|
+
list [--chat-id <id>] → list all jobs (sorted by createdAt desc).
|
|
18
|
+
--chat-id filters to jobs that push to that
|
|
19
|
+
chat — useful for "delete my cron" inside a
|
|
20
|
+
channel where the agent only knows the
|
|
21
|
+
current chatId.
|
|
22
|
+
get <id> → one job + last 5 runs
|
|
23
|
+
create <args> → insert a new job, prints the generated id
|
|
24
|
+
update <id> <args> → patch an existing job
|
|
25
|
+
enable <id> / disable <id> → toggle the `enabled` flag
|
|
26
|
+
delete <id> → remove the job and its run history
|
|
27
|
+
channels → list bound channel accounts that can be
|
|
28
|
+
used as cron targets (for `--targets`)
|
|
29
|
+
runs <jobId> [--limit N] [--before <runId>]
|
|
30
|
+
→ run history, cursor-paginated by runId
|
|
31
|
+
(which is an ISO timestamp = sort order)
|
|
32
|
+
|
|
33
|
+
`--targets` arg: comma-separated `channelType:accountId` pairs.
|
|
34
|
+
example: `--targets telegram:halo_agent_bot,wechat:alice`
|
|
35
|
+
"""
|
|
36
|
+
import argparse
|
|
37
|
+
import json
|
|
38
|
+
import os
|
|
39
|
+
import sqlite3
|
|
40
|
+
import sys
|
|
41
|
+
import time
|
|
42
|
+
from pathlib import Path
|
|
43
|
+
|
|
44
|
+
DB = Path.home() / '.halo' / 'global' / 'cron.db'
|
|
45
|
+
CHANNELS_DB = Path.home() / '.halo' / 'secrets' / 'channels' / 'channels.db'
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def open_db() -> sqlite3.Connection:
|
|
49
|
+
if not DB.exists():
|
|
50
|
+
die(f'cron db not found at {DB}; is the halo server running and initialized?')
|
|
51
|
+
conn = sqlite3.connect(str(DB))
|
|
52
|
+
conn.row_factory = sqlite3.Row
|
|
53
|
+
return conn
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def die(msg: str, code: int = 1):
|
|
57
|
+
sys.stderr.write(f'error: {msg}\n')
|
|
58
|
+
sys.exit(code)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def now_ms() -> int:
|
|
62
|
+
return int(time.time() * 1000)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def gen_job_id() -> str:
|
|
66
|
+
"""Match the admin route's id format: cron-<timestamp36>-<rand6>."""
|
|
67
|
+
import random
|
|
68
|
+
import string
|
|
69
|
+
ts = format(int(time.time() * 1000), 'x')[-8:]
|
|
70
|
+
rand = ''.join(random.choices(string.ascii_lowercase + string.digits, k=6))
|
|
71
|
+
return f'cron-{ts}-{rand}'
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def parse_targets(raw: str) -> list[dict]:
|
|
75
|
+
"""`telegram:bot1,wechat:user2` → [{channelType, accountId}, ...].
|
|
76
|
+
|
|
77
|
+
Optional explicit chat id: `telegram:bot1:12345` — useful when the
|
|
78
|
+
cron was created from inside a chat and should keep replying there
|
|
79
|
+
instead of fanning out to every whitelisted user. Accepted forms:
|
|
80
|
+
|
|
81
|
+
telegram:bot1 → fan-out (telegram allowedUsers)
|
|
82
|
+
telegram:bot1:12345 → only chat 12345
|
|
83
|
+
wechat:owner_id → bot owner (QR-bind userId)
|
|
84
|
+
wechat:owner_id:o123 → only that openId
|
|
85
|
+
|
|
86
|
+
Also accepts a JSON array for callers that want to set complex shapes.
|
|
87
|
+
"""
|
|
88
|
+
raw = raw.strip()
|
|
89
|
+
if not raw:
|
|
90
|
+
return []
|
|
91
|
+
if raw.startswith('['):
|
|
92
|
+
return json.loads(raw)
|
|
93
|
+
out = []
|
|
94
|
+
for token in raw.split(','):
|
|
95
|
+
token = token.strip()
|
|
96
|
+
if not token:
|
|
97
|
+
continue
|
|
98
|
+
if ':' not in token:
|
|
99
|
+
die(f'invalid target "{token}": expected "channelType:accountId[:chatId]"')
|
|
100
|
+
parts = token.split(':', 2)
|
|
101
|
+
ct, aid = parts[0].strip(), parts[1].strip()
|
|
102
|
+
item: dict = {'channelType': ct, 'accountId': aid}
|
|
103
|
+
if len(parts) == 3 and parts[2].strip():
|
|
104
|
+
item['chatId'] = parts[2].strip()
|
|
105
|
+
out.append(item)
|
|
106
|
+
return out
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def parse_run_at(raw: str | None) -> int | None:
|
|
110
|
+
"""Accepts ISO-8601 (`2026-05-24T09:00:00`) or a unix-ms integer; returns
|
|
111
|
+
epoch ms or None. Local time without offset is parsed as host TZ —
|
|
112
|
+
same convention as `datetime.fromisoformat`."""
|
|
113
|
+
if not raw:
|
|
114
|
+
return None
|
|
115
|
+
raw = raw.strip()
|
|
116
|
+
if not raw:
|
|
117
|
+
return None
|
|
118
|
+
if raw.isdigit():
|
|
119
|
+
return int(raw)
|
|
120
|
+
from datetime import datetime
|
|
121
|
+
try:
|
|
122
|
+
dt = datetime.fromisoformat(raw)
|
|
123
|
+
except ValueError:
|
|
124
|
+
die(f'invalid --run-at "{raw}": expected ISO-8601 (e.g. 2026-05-24T09:00) or unix ms')
|
|
125
|
+
if dt.tzinfo is None:
|
|
126
|
+
dt = dt.astimezone() # treat as host-local
|
|
127
|
+
return int(dt.timestamp() * 1000)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def row_to_job(row: sqlite3.Row) -> dict:
|
|
131
|
+
j = dict(row)
|
|
132
|
+
try:
|
|
133
|
+
j['targets'] = json.loads(j.get('targets') or '[]')
|
|
134
|
+
except Exception:
|
|
135
|
+
j['targets'] = []
|
|
136
|
+
j['enabled'] = bool(j['enabled'])
|
|
137
|
+
return j
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# ── subcommand impls ──────────────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def cmd_list(args):
|
|
144
|
+
conn = open_db()
|
|
145
|
+
rows = conn.execute(
|
|
146
|
+
'SELECT * FROM cron_jobs ORDER BY created_at DESC'
|
|
147
|
+
).fetchall()
|
|
148
|
+
jobs = [row_to_job(r) for r in rows]
|
|
149
|
+
# Filter to jobs whose targets push to this chat — used by the
|
|
150
|
+
# in-channel "delete my cron" / "list my crons" flow, where the
|
|
151
|
+
# agent only knows the current chatId and needs to find which
|
|
152
|
+
# cron_jobs reference it. chatId formats don't collide across
|
|
153
|
+
# channels (telegram numeric, slack D…/C…, feishu oc_…, wechat o…)
|
|
154
|
+
# so a single chatId match is unambiguous.
|
|
155
|
+
if args.chat_id:
|
|
156
|
+
jobs = [
|
|
157
|
+
j for j in jobs
|
|
158
|
+
if any((t.get('chatId') or '') == args.chat_id for t in j.get('targets') or [])
|
|
159
|
+
]
|
|
160
|
+
print(json.dumps(jobs, ensure_ascii=False, indent=2))
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def cmd_get(args):
|
|
164
|
+
conn = open_db()
|
|
165
|
+
row = conn.execute(
|
|
166
|
+
'SELECT * FROM cron_jobs WHERE id = ?', (args.id,)
|
|
167
|
+
).fetchone()
|
|
168
|
+
if not row:
|
|
169
|
+
die(f'job {args.id} not found', 2)
|
|
170
|
+
job = row_to_job(row)
|
|
171
|
+
runs = conn.execute(
|
|
172
|
+
'SELECT id, status, started_at, completed_at, exit_code, failure_reason '
|
|
173
|
+
'FROM cron_runs WHERE job_id = ? ORDER BY id DESC LIMIT 5',
|
|
174
|
+
(args.id,),
|
|
175
|
+
).fetchall()
|
|
176
|
+
job['recentRuns'] = [dict(r) for r in runs]
|
|
177
|
+
print(json.dumps(job, ensure_ascii=False, indent=2))
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def cmd_create(args):
|
|
181
|
+
if not args.workspace:
|
|
182
|
+
die('--workspace required')
|
|
183
|
+
if not args.prompt:
|
|
184
|
+
die('--prompt required')
|
|
185
|
+
has_schedule = bool(args.schedule and args.schedule.strip())
|
|
186
|
+
run_at_ms = parse_run_at(args.run_at)
|
|
187
|
+
if not has_schedule and run_at_ms is None:
|
|
188
|
+
die('--schedule or --run-at required')
|
|
189
|
+
if has_schedule and run_at_ms is not None:
|
|
190
|
+
die('--schedule and --run-at are mutually exclusive')
|
|
191
|
+
if run_at_ms is not None and run_at_ms <= now_ms():
|
|
192
|
+
die('--run-at must be in the future')
|
|
193
|
+
|
|
194
|
+
job_id = args.id or gen_job_id()
|
|
195
|
+
now = now_ms()
|
|
196
|
+
targets = parse_targets(args.targets) if args.targets else []
|
|
197
|
+
|
|
198
|
+
conn = open_db()
|
|
199
|
+
existing = conn.execute('SELECT 1 FROM cron_jobs WHERE id = ?', (job_id,)).fetchone()
|
|
200
|
+
if existing:
|
|
201
|
+
die(f'job id {job_id} already exists; pick another with --id', 2)
|
|
202
|
+
|
|
203
|
+
conn.execute(
|
|
204
|
+
'INSERT INTO cron_jobs(id, label, workspace_path, agent_id, user_prompt, schedule, '
|
|
205
|
+
'run_at, timezone, targets, enabled, created_at, updated_at) '
|
|
206
|
+
'VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
|
207
|
+
(
|
|
208
|
+
job_id,
|
|
209
|
+
args.label,
|
|
210
|
+
args.workspace,
|
|
211
|
+
args.agent or 'default',
|
|
212
|
+
args.prompt,
|
|
213
|
+
args.schedule or '',
|
|
214
|
+
run_at_ms,
|
|
215
|
+
args.timezone,
|
|
216
|
+
json.dumps(targets, ensure_ascii=False),
|
|
217
|
+
0 if args.disabled else 1,
|
|
218
|
+
now, now,
|
|
219
|
+
),
|
|
220
|
+
)
|
|
221
|
+
conn.commit()
|
|
222
|
+
print(json.dumps({'id': job_id, 'created': True}, ensure_ascii=False))
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def cmd_update(args):
|
|
226
|
+
conn = open_db()
|
|
227
|
+
row = conn.execute('SELECT * FROM cron_jobs WHERE id = ?', (args.id,)).fetchone()
|
|
228
|
+
if not row:
|
|
229
|
+
die(f'job {args.id} not found', 2)
|
|
230
|
+
|
|
231
|
+
sets: list[tuple[str, object]] = []
|
|
232
|
+
if args.label is not None: sets.append(('label', args.label))
|
|
233
|
+
if args.workspace is not None: sets.append(('workspace_path', args.workspace))
|
|
234
|
+
if args.agent is not None: sets.append(('agent_id', args.agent))
|
|
235
|
+
if args.prompt is not None: sets.append(('user_prompt', args.prompt))
|
|
236
|
+
if args.schedule is not None: sets.append(('schedule', args.schedule))
|
|
237
|
+
if args.run_at is not None:
|
|
238
|
+
run_at_ms = parse_run_at(args.run_at)
|
|
239
|
+
if run_at_ms is not None and run_at_ms <= now_ms():
|
|
240
|
+
die('--run-at must be in the future')
|
|
241
|
+
sets.append(('run_at', run_at_ms))
|
|
242
|
+
if args.timezone is not None: sets.append(('timezone', args.timezone))
|
|
243
|
+
if args.targets is not None:
|
|
244
|
+
sets.append(('targets', json.dumps(parse_targets(args.targets), ensure_ascii=False)))
|
|
245
|
+
if not sets:
|
|
246
|
+
die('nothing to update — pass at least one --label/--workspace/--agent/--prompt/--schedule/--run-at/--timezone/--targets')
|
|
247
|
+
sets.append(('updated_at', now_ms()))
|
|
248
|
+
|
|
249
|
+
placeholders = ', '.join(f'{k} = ?' for k, _ in sets)
|
|
250
|
+
values = [v for _, v in sets] + [args.id]
|
|
251
|
+
conn.execute(f'UPDATE cron_jobs SET {placeholders} WHERE id = ?', values)
|
|
252
|
+
conn.commit()
|
|
253
|
+
print(json.dumps({'id': args.id, 'updated': True}, ensure_ascii=False))
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def cmd_enable(args):
|
|
257
|
+
_toggle_enabled(args.id, True)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def cmd_disable(args):
|
|
261
|
+
_toggle_enabled(args.id, False)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _toggle_enabled(job_id: str, on: bool):
|
|
265
|
+
conn = open_db()
|
|
266
|
+
cur = conn.execute(
|
|
267
|
+
'UPDATE cron_jobs SET enabled = ?, updated_at = ? WHERE id = ?',
|
|
268
|
+
(1 if on else 0, now_ms(), job_id),
|
|
269
|
+
)
|
|
270
|
+
if cur.rowcount == 0:
|
|
271
|
+
die(f'job {job_id} not found', 2)
|
|
272
|
+
conn.commit()
|
|
273
|
+
print(json.dumps({'id': job_id, 'enabled': on}, ensure_ascii=False))
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def cmd_delete(args):
|
|
277
|
+
conn = open_db()
|
|
278
|
+
cur = conn.execute('DELETE FROM cron_jobs WHERE id = ?', (args.id,))
|
|
279
|
+
if cur.rowcount == 0:
|
|
280
|
+
die(f'job {args.id} not found', 2)
|
|
281
|
+
conn.execute('DELETE FROM cron_runs WHERE job_id = ?', (args.id,))
|
|
282
|
+
conn.commit()
|
|
283
|
+
print(json.dumps({'id': args.id, 'deleted': True}, ensure_ascii=False))
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def cmd_channels(_):
|
|
287
|
+
"""List channel accounts that can be referenced as cron targets.
|
|
288
|
+
|
|
289
|
+
Mirrors the admin /api/cron/channel-targets endpoint:
|
|
290
|
+
telegram = ok if numeric id in allowedUsers OR a cached
|
|
291
|
+
lastActiveChatId; wechat = ok if account.userId (QR-bind owner)
|
|
292
|
+
OR cached lastActiveChatId.
|
|
293
|
+
"""
|
|
294
|
+
if not CHANNELS_DB.exists():
|
|
295
|
+
print(json.dumps([], ensure_ascii=False))
|
|
296
|
+
return
|
|
297
|
+
conn = sqlite3.connect(str(CHANNELS_DB))
|
|
298
|
+
conn.row_factory = sqlite3.Row
|
|
299
|
+
rows = conn.execute(
|
|
300
|
+
"SELECT account_id, channel_type, label, enabled, workspace_path, config "
|
|
301
|
+
"FROM channel_accounts WHERE enabled = 1"
|
|
302
|
+
).fetchall()
|
|
303
|
+
out = []
|
|
304
|
+
import re
|
|
305
|
+
for r in rows:
|
|
306
|
+
try:
|
|
307
|
+
cfg = json.loads(r['config'] or '{}')
|
|
308
|
+
except Exception:
|
|
309
|
+
cfg = {}
|
|
310
|
+
last = cfg.get('lastActiveChatId')
|
|
311
|
+
ct = r['channel_type']
|
|
312
|
+
if ct == 'telegram':
|
|
313
|
+
allowed = cfg.get('allowedUsers') or ''
|
|
314
|
+
has_whitelist_id = bool(re.search(r'\b-?\d+\b', allowed))
|
|
315
|
+
ready = has_whitelist_id or bool(last)
|
|
316
|
+
elif ct == 'wechat':
|
|
317
|
+
owner = cfg.get('userId')
|
|
318
|
+
ready = bool(owner) or bool(last)
|
|
319
|
+
else:
|
|
320
|
+
ready = bool(last)
|
|
321
|
+
out.append({
|
|
322
|
+
'channelType': ct,
|
|
323
|
+
'accountId': r['account_id'],
|
|
324
|
+
'label': r['label'] or r['account_id'],
|
|
325
|
+
'workspacePath': r['workspace_path'],
|
|
326
|
+
'ready': ready,
|
|
327
|
+
})
|
|
328
|
+
print(json.dumps(out, ensure_ascii=False, indent=2))
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def cmd_runs(args):
|
|
332
|
+
conn = open_db()
|
|
333
|
+
sql = (
|
|
334
|
+
'SELECT id, job_id, trigger_kind, status, started_at, completed_at, '
|
|
335
|
+
'exit_code, failure_reason, output, dispatch_results '
|
|
336
|
+
'FROM cron_runs WHERE job_id = ?'
|
|
337
|
+
)
|
|
338
|
+
params: list[object] = [args.job_id]
|
|
339
|
+
if args.before:
|
|
340
|
+
# runId is `<isoTimestamp>-<rand>`, sortable as a string. id < cursor
|
|
341
|
+
# is the "older than" check for descending pagination.
|
|
342
|
+
sql += ' AND id < ?'
|
|
343
|
+
params.append(args.before)
|
|
344
|
+
sql += ' ORDER BY id DESC LIMIT ?'
|
|
345
|
+
params.append(args.limit + 1) # fetch one extra to detect hasMore
|
|
346
|
+
rows = conn.execute(sql, params).fetchall()
|
|
347
|
+
has_more = len(rows) > args.limit
|
|
348
|
+
rows = rows[:args.limit]
|
|
349
|
+
runs = []
|
|
350
|
+
for r in rows:
|
|
351
|
+
d = dict(r)
|
|
352
|
+
try:
|
|
353
|
+
d['dispatch_results'] = json.loads(d['dispatch_results']) if d['dispatch_results'] else None
|
|
354
|
+
except Exception:
|
|
355
|
+
pass
|
|
356
|
+
runs.append(d)
|
|
357
|
+
out = {
|
|
358
|
+
'runs': runs,
|
|
359
|
+
'hasMore': has_more,
|
|
360
|
+
'nextCursor': runs[-1]['id'] if has_more and runs else None,
|
|
361
|
+
}
|
|
362
|
+
print(json.dumps(out, ensure_ascii=False, indent=2))
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
# ── argparse ──────────────────────────────────────────────────────────
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def main():
|
|
369
|
+
p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
370
|
+
sub = p.add_subparsers(dest='cmd', required=True)
|
|
371
|
+
|
|
372
|
+
ls = sub.add_parser('list')
|
|
373
|
+
ls.add_argument('--chat-id', dest='chat_id',
|
|
374
|
+
help='filter to jobs that push to this chat id (use {{channel.chat_id}} from inside a chat)')
|
|
375
|
+
ls.set_defaults(func=cmd_list)
|
|
376
|
+
|
|
377
|
+
g = sub.add_parser('get')
|
|
378
|
+
g.add_argument('id')
|
|
379
|
+
g.set_defaults(func=cmd_get)
|
|
380
|
+
|
|
381
|
+
c = sub.add_parser('create')
|
|
382
|
+
c.add_argument('--id', help='custom job id (default: auto-generated)')
|
|
383
|
+
c.add_argument('--label')
|
|
384
|
+
c.add_argument('--workspace', required=True, help='absolute workspace path')
|
|
385
|
+
c.add_argument('--agent', help="agent id (default: 'default')")
|
|
386
|
+
c.add_argument('--prompt', required=True)
|
|
387
|
+
c.add_argument('--schedule', help='5-field cron expression (recurring); mutually exclusive with --run-at')
|
|
388
|
+
c.add_argument('--run-at', dest='run_at',
|
|
389
|
+
help='one-shot fire time, ISO-8601 (e.g. 2026-05-24T09:00) or unix ms — auto-disables after fire')
|
|
390
|
+
c.add_argument('--timezone', help='IANA tz, e.g. Asia/Shanghai')
|
|
391
|
+
c.add_argument('--targets',
|
|
392
|
+
help='comma-separated channelType:accountId[:chatId] list, or JSON array. '
|
|
393
|
+
'chatId pins delivery to a specific chat (e.g. when scheduling from inside a chat).')
|
|
394
|
+
c.add_argument('--disabled', action='store_true', help='create paused (default: enabled)')
|
|
395
|
+
c.set_defaults(func=cmd_create)
|
|
396
|
+
|
|
397
|
+
u = sub.add_parser('update')
|
|
398
|
+
u.add_argument('id')
|
|
399
|
+
u.add_argument('--label')
|
|
400
|
+
u.add_argument('--workspace')
|
|
401
|
+
u.add_argument('--agent')
|
|
402
|
+
u.add_argument('--prompt')
|
|
403
|
+
u.add_argument('--schedule')
|
|
404
|
+
u.add_argument('--run-at', dest='run_at')
|
|
405
|
+
u.add_argument('--timezone')
|
|
406
|
+
u.add_argument('--targets')
|
|
407
|
+
u.set_defaults(func=cmd_update)
|
|
408
|
+
|
|
409
|
+
e = sub.add_parser('enable')
|
|
410
|
+
e.add_argument('id')
|
|
411
|
+
e.set_defaults(func=cmd_enable)
|
|
412
|
+
|
|
413
|
+
d = sub.add_parser('disable')
|
|
414
|
+
d.add_argument('id')
|
|
415
|
+
d.set_defaults(func=cmd_disable)
|
|
416
|
+
|
|
417
|
+
rm = sub.add_parser('delete')
|
|
418
|
+
rm.add_argument('id')
|
|
419
|
+
rm.set_defaults(func=cmd_delete)
|
|
420
|
+
|
|
421
|
+
sub.add_parser('channels').set_defaults(func=cmd_channels)
|
|
422
|
+
|
|
423
|
+
rs = sub.add_parser('runs')
|
|
424
|
+
rs.add_argument('job_id')
|
|
425
|
+
rs.add_argument('--limit', type=int, default=20)
|
|
426
|
+
rs.add_argument('--before', help='runId cursor; pages older runs')
|
|
427
|
+
rs.set_defaults(func=cmd_runs)
|
|
428
|
+
|
|
429
|
+
args = p.parse_args()
|
|
430
|
+
args.func(args)
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
if __name__ == '__main__':
|
|
434
|
+
main()
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: halo
|
|
3
|
+
description: Platform self-knowledge for Halo — configuration directories, MD file scopes, USER.md schema, and the bundled-doc lookup table. Activate when the user asks anything about Halo itself (how to configure it, where files live, how agents/skills/INSTRUCTIONS work, what doc to read for a given topic) or when you need to edit USER.md / INSTRUCTIONS.md / AGENT.md and want the scope rule before writing.
|
|
4
|
+
user-invocable: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Halo Platform Knowledge
|
|
8
|
+
|
|
9
|
+
You are running inside **Halo**, a multi-agent collaboration workspace.
|
|
10
|
+
|
|
11
|
+
## Configuration Directories
|
|
12
|
+
|
|
13
|
+
| Location | Scope | Purpose |
|
|
14
|
+
|----------|-------|---------|
|
|
15
|
+
| `~/.halo/global/` | Global (all projects) | User profile, global instructions, global agents & skills, system prompts, bundled docs |
|
|
16
|
+
| `<workspace>/.halo/` | Per-project | Project instructions, knowledge index, project agents & skills |
|
|
17
|
+
|
|
18
|
+
`.halo/` is excluded from `grep` and `glob`. Use `file_read` with the exact path to access files inside.
|
|
19
|
+
|
|
20
|
+
## MD Files
|
|
21
|
+
|
|
22
|
+
| File | Purpose | Paths |
|
|
23
|
+
|------|---------|-------|
|
|
24
|
+
| **USER.md** | User profile (names, language, communication style) | `~/.halo/global/USER.md`; `<workspace>/.halo/USER.md` (override) |
|
|
25
|
+
| **AGENT.md** | Agent personality and behavior | `~/.halo/global/agents/<id>/AGENT.md`; `<workspace>/.halo/agents/<id>/AGENT.md` (override) |
|
|
26
|
+
| **INSTRUCTIONS.md** | User preferences injected into every agent | `~/.halo/global/INSTRUCTIONS.md`; `<workspace>/.halo/INSTRUCTIONS.md`; `<workspace>/<subdir>/.halo/INSTRUCTIONS.md` (cumulative per depth) |
|
|
27
|
+
| **INDEX.md** | Project documentation index | `<workspace>/.halo/INDEX.md` |
|
|
28
|
+
| **System prompts** | Platform-level instructions | `~/.halo/global/prompts/{bootstrap,all,root}/*.md` |
|
|
29
|
+
|
|
30
|
+
Changes take effect on the next conversation or session reset.
|
|
31
|
+
|
|
32
|
+
## Organizing the workspace
|
|
33
|
+
|
|
34
|
+
The `workspace` skill handles both initial setup and ongoing cleanup of `.halo/INDEX.md`, `INSTRUCTIONS.md`, and `memory/`.
|
|
35
|
+
|
|
36
|
+
- **No INDEX.md yet** and the user starts engaging with the project's structure or goals (not casual browsing) → suggest `/workspace setup`. Drafts INDEX.md + INSTRUCTIONS.md from the README and a few questions.
|
|
37
|
+
- **INDEX.md already exists** and the user wants to "clean up / reorganize" the workspace → `/workspace tidy`. Reviews what's there, prunes stale entries, fixes broken links, and reshapes sections without rewriting from scratch.
|
|
38
|
+
|
|
39
|
+
## Editing Scope Rule
|
|
40
|
+
|
|
41
|
+
USER.md / INSTRUCTIONS.md / AGENT.md exist at multiple scopes (global vs workspace). When editing:
|
|
42
|
+
|
|
43
|
+
1. If a workspace file exists, edit that one
|
|
44
|
+
2. Otherwise edit the global file
|
|
45
|
+
3. If ambiguous, ask the user
|
|
46
|
+
|
|
47
|
+
Creating a new override in the wrong scope (e.g. writing to global when a workspace file already shadows it) hides the user's edits at runtime.
|
|
48
|
+
|
|
49
|
+
## Managing User Profile
|
|
50
|
+
|
|
51
|
+
USER.md schema:
|
|
52
|
+
|
|
53
|
+
```markdown
|
|
54
|
+
---
|
|
55
|
+
user_name: [their name]
|
|
56
|
+
ai_name: [your name]
|
|
57
|
+
lang: [e.g. zh-CN, en]
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Communication Style
|
|
61
|
+
[Preferences]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
To update: apply the scope rule above, `file_read` the target, update fields, `file_write` back. Effective next conversation.
|
|
65
|
+
|
|
66
|
+
## Platform Documentation
|
|
67
|
+
|
|
68
|
+
Bundled docs: `~/.halo/global/docs/`. These reflect the installed Halo version; training data may be outdated. For platform questions, pick the row below and `file_read` that file.
|
|
69
|
+
|
|
70
|
+
What's bundled (and what's not):
|
|
71
|
+
|
|
72
|
+
- `guide/` — end-user guides (8 files, see table below)
|
|
73
|
+
- `dev/` — developer-facing topics (3 files: tools, add-model-provider, add-channel)
|
|
74
|
+
- `requirements/` — product requirements (2 files: settings, command)
|
|
75
|
+
- `design/`, `plans/`, `test/` — **not bundled** (live in the source repo only). Cross-links from bundled docs into these paths will 404; answer from the doc body, not the link.
|
|
76
|
+
|
|
77
|
+
| User asks … | Read |
|
|
78
|
+
|---|---|
|
|
79
|
+
| How do I log in / open a workspace? | `guide/getting-started.md` |
|
|
80
|
+
| UI / keyboard shortcuts? | `guide/workspace.md` |
|
|
81
|
+
| Chat panel, @ mentions, slash commands, context injection? | `guide/chat.md` |
|
|
82
|
+
| Where do I see past conversations? | `guide/sessions.md` |
|
|
83
|
+
| Create / edit / delete an agent? AGENT.md vs agent.yaml? Global vs workspace? Thinking / prompt caching? | `guide/agents.md` |
|
|
84
|
+
| What tools can an agent use? How do I add one? | `dev/tools.md` |
|
|
85
|
+
| What is a skill? How do I create / test one? | `guide/skills.md` + `guide/testing-agents-and-skills.md` |
|
|
86
|
+
| How do I test the agent I just made? | `guide/testing-agents-and-skills.md` |
|
|
87
|
+
| Slash commands (how they work, registering) | `guide/skills.md` (skill-as-command) + `requirements/command.md` |
|
|
88
|
+
| `{{params.x.y}}` placeholders? | `guide/skills.md` (Placeholders section) |
|
|
89
|
+
| Where do I put API keys / credentials? | `guide/secrets-and-credentials.md` |
|
|
90
|
+
| What goes in settings.yaml? | `requirements/settings.md` |
|
|
91
|
+
| Add a new model provider (OpenAI / Gemini / …)? | `dev/add-model-provider.md` |
|
|
92
|
+
| Add a new IM / chat channel (Slack / Discord / …)? | `dev/add-channel.md` |
|
|
93
|
+
|
|
94
|
+
Questions outside these rows aren't in the bundled docs (backend internals, REST API reference, design specs live in the Halo source repo). Read the code or tell the user you don't know.
|
|
95
|
+
|
|
96
|
+
## Skills
|
|
97
|
+
|
|
98
|
+
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`.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nova-web-search
|
|
3
|
+
description: Real-time web search using Amazon Nova 2 Lite's built-in nova_grounding system tool. Use this skill when the user asks about current events, news, latest updates, prices, or any real-time information. This replaces the web_search tool.
|
|
4
|
+
user-invocable: false
|
|
5
|
+
---
|
|
6
|
+
# Nova Web Search
|
|
7
|
+
|
|
8
|
+
Perform real-time web search using Amazon Nova 2 Lite's built-in `nova_grounding` system tool. Returns a synthesized answer with source URLs.
|
|
9
|
+
|
|
10
|
+
## How to Use
|
|
11
|
+
|
|
12
|
+
Run the following Python script via `shell_exec`:
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
import boto3
|
|
16
|
+
from botocore.config import Config
|
|
17
|
+
|
|
18
|
+
bedrock = boto3.client("bedrock-runtime", region_name="us-east-1", config=Config(read_timeout=60))
|
|
19
|
+
|
|
20
|
+
query = "YOUR_QUERY_HERE" # Replace with the actual search query
|
|
21
|
+
|
|
22
|
+
response = bedrock.converse(
|
|
23
|
+
modelId="us.amazon.nova-2-lite-v1:0",
|
|
24
|
+
messages=[{"role": "user", "content": [{"text": query}]}],
|
|
25
|
+
toolConfig={"tools": [{"systemTool": {"name": "nova_grounding"}}]}
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
answer = ""
|
|
29
|
+
sources = []
|
|
30
|
+
for content in response["output"]["message"]["content"]:
|
|
31
|
+
if "text" in content:
|
|
32
|
+
answer += content["text"]
|
|
33
|
+
elif "citationsContent" in content:
|
|
34
|
+
for c in content["citationsContent"].get("citations", []):
|
|
35
|
+
url = c.get("location", {}).get("web", {}).get("url", "")
|
|
36
|
+
if url and url not in sources:
|
|
37
|
+
sources.append(url)
|
|
38
|
+
|
|
39
|
+
print(answer)
|
|
40
|
+
if sources:
|
|
41
|
+
print("\nSources:", sources)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Query Formulation
|
|
45
|
+
|
|
46
|
+
Nova grounds better against full natural-language questions than against
|
|
47
|
+
keyword stubs — raw keywords return more loosely-related sources, while
|
|
48
|
+
a question gives Nova enough context to filter.
|
|
49
|
+
|
|
50
|
+
| User Intent | Query Template |
|
|
51
|
+
|---|---|
|
|
52
|
+
| News / updates / latest developments | `"What are the latest news about X?"` |
|
|
53
|
+
| Product / specs / concept lookup | `"Tell me about X, including specs, price, and latest updates."` |
|
|
54
|
+
| General search | `"Search for information about X."` |
|
|
55
|
+
|
|
56
|
+
**Examples:**
|
|
57
|
+
- ✅ `"What are the latest news about the Middle East conflict?"`
|
|
58
|
+
- ✅ `"Tell me about the NVIDIA RTX 5080, including specs and price."`
|
|
59
|
+
- ✗ `"RTX 5080"` — terse keywords leave Nova guessing at intent
|
|
60
|
+
|
|
61
|
+
## Requirements
|
|
62
|
+
|
|
63
|
+
- Region **must** be `us-east-1`
|
|
64
|
+
- English queries yield better results than other languages
|
|
65
|
+
- Model ID: `us.amazon.nova-2-lite-v1:0`
|