@turmind/halo 1.6.1 → 1.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin-out/404.html +1 -1
- package/admin-out/_next/static/chunks/app/page-1d27029caba48956.js +1 -0
- package/admin-out/index.html +1 -1
- package/admin-out/index.txt +2 -2
- package/bundled-docs/design/canvas-extensions.md +1 -1
- package/bundled-docs/design/cron.md +2 -0
- package/bundled-docs/design/storage.md +5 -3
- package/bundled-docs/design/ws.md +6 -4
- package/bundled-docs/dev/add-model-provider.md +2 -2
- package/bundled-docs/dev/api.md +2 -2
- package/bundled-docs/requirements/chat.md +2 -2
- package/bundled-docs/requirements/session.md +1 -1
- package/dist/index.js +84 -39
- package/package.json +1 -1
- package/templates/models/deepseek.yaml +15 -10
- package/templates/models/hunyuan.yaml +11 -7
- package/templates/models/qwen.yaml +23 -12
- package/templates/skills/cron/SKILL.md +2 -1
- package/templates/skills/cron/manage-cron.py +25 -8
- package/admin-out/_next/static/chunks/app/page-be47983ba8ff20b1.js +0 -1
- /package/admin-out/_next/static/{j6buZnQDPISEFgtAuTGyl → VgAe62AuGEiYrF4JQ7WQe}/_buildManifest.js +0 -0
- /package/admin-out/_next/static/{j6buZnQDPISEFgtAuTGyl → VgAe62AuGEiYrF4JQ7WQe}/_ssgManifest.js +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
id: deepseek
|
|
2
2
|
runtime: deepseek
|
|
3
|
-
revision:
|
|
3
|
+
revision: 2026101101
|
|
4
4
|
displayName: DeepSeek
|
|
5
5
|
displayName_zh: DeepSeek
|
|
6
6
|
description: DeepSeek V4 via OpenAI-compatible chat completions API
|
|
@@ -15,9 +15,18 @@ secrets:
|
|
|
15
15
|
secret: true
|
|
16
16
|
|
|
17
17
|
# Capabilities verified via live probes (2026-05-27, re-probed 2026-09-24).
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
18
|
+
# Caching is automatic; cached_tokens shows up from turn 2.
|
|
19
|
+
#
|
|
20
|
+
# Thinking (re-probed 2026-10-11, api-docs.deepseek.com/guides/thinking_mode +
|
|
21
|
+
# GET /models): `thinking:{type:enabled|disabled}` toggles, on by default;
|
|
22
|
+
# `reasoning_effort` grades it. The API's real levels are low / high / max
|
|
23
|
+
# (/models `effort.supported_levels`, default high); it also accepts none /
|
|
24
|
+
# minimal / medium / xhigh / ultra and folds them itself (minimal→low,
|
|
25
|
+
# medium/xhigh→high, ultra→max) — all 200 live, a bogus value 422s with that
|
|
26
|
+
# list. So the presets are the three real levels and deepseek-agent.ts passes
|
|
27
|
+
# the label through unmapped: an older agent.yaml with medium / xhigh keeps
|
|
28
|
+
# working at high. Before 2026-10-11 the runtime sent no reasoning_effort at
|
|
29
|
+
# all (every agent ran at the API default, high).
|
|
21
30
|
#
|
|
22
31
|
# Vision is per model: `image_url` blocks are no longer rejected at the
|
|
23
32
|
# deserialize layer on either id, but only `deepseek-flash` (V4.1-Flash,
|
|
@@ -42,13 +51,11 @@ models:
|
|
|
42
51
|
- { value: 5m, label: 5min }
|
|
43
52
|
thinking:
|
|
44
53
|
defaultEnabled: true
|
|
45
|
-
default:
|
|
54
|
+
default: high
|
|
46
55
|
effortPresets:
|
|
47
56
|
- { value: low, label: Low }
|
|
48
|
-
- { value: medium, label: Medium }
|
|
49
57
|
- { value: high, label: High }
|
|
50
58
|
- { value: max, label: Max }
|
|
51
|
-
- { value: xhigh, label: XHigh }
|
|
52
59
|
|
|
53
60
|
# DeepSeek retired the versioned flash ids (deepseek-v4-flash,
|
|
54
61
|
# deepseek-v4-flash-vision-exp) in favour of a rolling `deepseek-flash`
|
|
@@ -70,10 +77,8 @@ models:
|
|
|
70
77
|
- { value: 5m, label: 5min }
|
|
71
78
|
thinking:
|
|
72
79
|
defaultEnabled: true
|
|
73
|
-
default:
|
|
80
|
+
default: high
|
|
74
81
|
effortPresets:
|
|
75
82
|
- { value: low, label: Low }
|
|
76
|
-
- { value: medium, label: Medium }
|
|
77
83
|
- { value: high, label: High }
|
|
78
84
|
- { value: max, label: Max }
|
|
79
|
-
- { value: xhigh, label: XHigh }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
id: hunyuan
|
|
2
2
|
runtime: hunyuan
|
|
3
|
-
revision:
|
|
3
|
+
revision: 2026101101
|
|
4
4
|
displayName: Hunyuan (Tencent)
|
|
5
5
|
displayName_zh: 混元(腾讯)
|
|
6
6
|
description: Tencent Hunyuan Hy4 preview / Hy3 on TokenHub — agent-oriented MoE with no_think / think_low / think_high modes
|
|
@@ -42,14 +42,18 @@ secrets:
|
|
|
42
42
|
# - Caching re-verified on hy3 / hy4-preview (turn 2 cached_tokens ≈ prompt).
|
|
43
43
|
# - Tool calls verified on hy3 / hy4-preview. Image: hy3 / hy4-preview both
|
|
44
44
|
# answered wrongly about a solid red PNG — keep image:false.
|
|
45
|
-
# - Max output:
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
45
|
+
# - Max output: the gateway accepted max_tokens 2,000,000 without error, so
|
|
46
|
+
# a rejection probe proves nothing.
|
|
47
|
+
# 2026-10-11: the TokenHub model list (cloud.tencent.com/document/product/1823/130051,
|
|
48
|
+
# updated 2026-09-28, 语言模型 table: 上下文窗口 / 最大输入 / 最大输出) gives
|
|
49
|
+
# Hy4 preview 1M / 960k / 64k and Hy3 256k / 192k / 128k → maxOutputTokens
|
|
50
|
+
# 65536 / 131072. hy3-preview is not on that page → stays at 16384. (One
|
|
51
|
+
# long-output run on hy3 with max_tokens 200000 stopped at 147,389 completion
|
|
52
|
+
# tokens, finish_reason length — above the doc figure; the doc value is used.)
|
|
49
53
|
models:
|
|
50
54
|
- id: hy3
|
|
51
55
|
displayName: Hunyuan Hy3
|
|
52
|
-
maxOutputTokens:
|
|
56
|
+
maxOutputTokens: 131072
|
|
53
57
|
contextWindow: 262144
|
|
54
58
|
maxContextWindow: 262144
|
|
55
59
|
compressAt: 0.9
|
|
@@ -77,7 +81,7 @@ models:
|
|
|
77
81
|
|
|
78
82
|
- id: hy4-preview
|
|
79
83
|
displayName: Hunyuan Hy4 Preview
|
|
80
|
-
maxOutputTokens:
|
|
84
|
+
maxOutputTokens: 65536
|
|
81
85
|
contextWindow: 272000
|
|
82
86
|
maxContextWindow: 1048576
|
|
83
87
|
compressAt: 0.9
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
id: qwen
|
|
2
2
|
runtime: qwen
|
|
3
|
-
revision:
|
|
3
|
+
revision: 2026101101
|
|
4
4
|
displayName: Qwen (Aliyun)
|
|
5
5
|
displayName_zh: 通义千问(阿里云)
|
|
6
6
|
description: Qwen3 family on Aliyun Bailian via the Anthropic-compatible Messages API
|
|
@@ -32,20 +32,31 @@ secrets:
|
|
|
32
32
|
# despite docs saying a vision-specific model is required);
|
|
33
33
|
# qwen3.7-max returned `Unexpected item type in content` (not
|
|
34
34
|
# supported). So image:false on max, image:true on plus.
|
|
35
|
-
# - effort presets: pure Halo-side
|
|
36
|
-
# table as bedrock-agent.ts via
|
|
35
|
+
# - effort presets on the manual (3.7 / 3.6) models: pure Halo-side
|
|
36
|
+
# abstraction. Same effort→budget table as bedrock-agent.ts via
|
|
37
|
+
# qwen-agent.ts's effortToBudget().
|
|
37
38
|
# - temperature range: doc says [0, 2) instead of Anthropic's [0, 1] —
|
|
38
39
|
# doesn't affect Halo since we don't expose temperature in the form.
|
|
39
40
|
# 2026-10-10 census (help.aliyun.com/zh/model-studio/anthropic-api-messages +
|
|
40
41
|
# the text-generation model guide, both list 3.8-max / 3.8-flash / 3.7-plus as
|
|
41
42
|
# current, 1M context): live-probed on the Anthropic endpoint — manual
|
|
42
43
|
# thinking.budget_tokens still accepted (the doc marks it "即将废弃" in favour
|
|
43
|
-
# of output_config.effort
|
|
44
|
+
# of output_config.effort — see 2026-10-11 below), thinking
|
|
44
45
|
# disabled honored, tool_use OK, image OK, cache_control ephemeral hit on
|
|
45
46
|
# turn 2. `ttl: '1h'` is accepted but the entry is still written as
|
|
46
47
|
# ephemeral_5m, so 5m stays the only TTL preset. Max output isn't published
|
|
47
48
|
# per model on these pages — maxOutputTokens stays 16384. 3.7-max / 3.6-plus
|
|
48
49
|
# are not on the deprecation page, kept below the new generation.
|
|
50
|
+
# 2026-10-11 (help.aliyun.com/zh/model-studio/anthropic-api-messages,
|
|
51
|
+
# `output_config.effort`): the doc lists effort values for qwen3.8-max /
|
|
52
|
+
# qwen3.8-flash only — xhigh (default) / medium / low, with max and high
|
|
53
|
+
# folded onto xhigh by the API. So 3.8 is `mode: adaptive`: qwen-agent sends
|
|
54
|
+
# thinking:{type:'enabled'} + output_config.effort. An agent.yaml that already
|
|
55
|
+
# carries an explicit budget_tokens keeps sending it (still accepted). For 3.8
|
|
56
|
+
# the same doc says max_tokens caps reply + thinking together. The 3.7 / 3.6
|
|
57
|
+
# models have no documented effort values → they stay `mode: manual`
|
|
58
|
+
# (budget_tokens). Thinking-field omitted = thinking on for every model here,
|
|
59
|
+
# so off is always sent as type:'disabled'.
|
|
49
60
|
models:
|
|
50
61
|
- id: qwen3.8-max
|
|
51
62
|
displayName: Qwen3.8 Max
|
|
@@ -63,13 +74,13 @@ models:
|
|
|
63
74
|
ttlPresets:
|
|
64
75
|
- { value: 5m, label: 5min }
|
|
65
76
|
thinking:
|
|
66
|
-
mode:
|
|
77
|
+
mode: adaptive
|
|
67
78
|
defaultEnabled: true
|
|
68
|
-
default:
|
|
69
|
-
defaultBudgetTokens: 8192
|
|
79
|
+
default: xhigh
|
|
70
80
|
effortPresets:
|
|
81
|
+
- { value: low, label: Low }
|
|
71
82
|
- { value: medium, label: Medium }
|
|
72
|
-
- { value:
|
|
83
|
+
- { value: xhigh, label: XHigh }
|
|
73
84
|
|
|
74
85
|
- id: qwen3.7-plus
|
|
75
86
|
displayName: Qwen3.7 Plus
|
|
@@ -111,13 +122,13 @@ models:
|
|
|
111
122
|
ttlPresets:
|
|
112
123
|
- { value: 5m, label: 5min }
|
|
113
124
|
thinking:
|
|
114
|
-
mode:
|
|
125
|
+
mode: adaptive
|
|
115
126
|
defaultEnabled: true
|
|
116
|
-
default:
|
|
117
|
-
defaultBudgetTokens: 8192
|
|
127
|
+
default: xhigh
|
|
118
128
|
effortPresets:
|
|
129
|
+
- { value: low, label: Low }
|
|
119
130
|
- { value: medium, label: Medium }
|
|
120
|
-
- { value:
|
|
131
|
+
- { value: xhigh, label: XHigh }
|
|
121
132
|
|
|
122
133
|
- id: qwen3.7-max
|
|
123
134
|
displayName: Qwen3.7 Max
|
|
@@ -79,7 +79,8 @@ Sensible defaults if user didn't say:
|
|
|
79
79
|
|
|
80
80
|
**One-shot (at-mode)** — pass `--run-at` instead of `--schedule`. The job
|
|
81
81
|
fires once at that instant and auto-disables. Useful for "remind me at 3pm
|
|
82
|
-
today" or "send the report next Monday morning".
|
|
82
|
+
today" or "send the report next Monday morning". On `update`, passing one
|
|
83
|
+
of the two switches the job's mode and clears the other; never pass both.
|
|
83
84
|
|
|
84
85
|
| Spoken | Flag |
|
|
85
86
|
|-----------------------------------|-------------------------------------------|
|
|
@@ -96,7 +96,10 @@ def parse_targets(raw: str) -> list[dict]:
|
|
|
96
96
|
if not raw:
|
|
97
97
|
return []
|
|
98
98
|
if raw.startswith('['):
|
|
99
|
-
|
|
99
|
+
try:
|
|
100
|
+
return json.loads(raw)
|
|
101
|
+
except ValueError as err:
|
|
102
|
+
die(f'invalid --targets JSON: {err}')
|
|
100
103
|
out = []
|
|
101
104
|
for token in raw.split(','):
|
|
102
105
|
token = token.strip()
|
|
@@ -127,7 +130,8 @@ def parse_timeout_sec(raw: str | None) -> int | None:
|
|
|
127
130
|
return v
|
|
128
131
|
|
|
129
132
|
|
|
130
|
-
|
|
133
|
+
# Always fullmatch: with `$`, re.match also accepts one trailing "\n".
|
|
134
|
+
SESSION_ID_RE = re.compile(r'[A-Za-z0-9_:-]{1,200}')
|
|
131
135
|
|
|
132
136
|
|
|
133
137
|
def parse_session(raw: str | None) -> str | None:
|
|
@@ -137,7 +141,7 @@ def parse_session(raw: str | None) -> str | None:
|
|
|
137
141
|
if raw is None or not raw.strip():
|
|
138
142
|
return None
|
|
139
143
|
v = raw.strip()
|
|
140
|
-
if not SESSION_ID_RE.
|
|
144
|
+
if not SESSION_ID_RE.fullmatch(v):
|
|
141
145
|
die(f'invalid --session "{raw}": expected a root session id (letters, digits, _ : - only; no ">" sub-session path)')
|
|
142
146
|
return v
|
|
143
147
|
|
|
@@ -393,6 +397,10 @@ def cmd_create(args):
|
|
|
393
397
|
active_until = parse_active_bound('--active-until', args.active_until or '', args.timezone)
|
|
394
398
|
validate_active_window(run_at_ms, active_from, active_until, new_until=True)
|
|
395
399
|
|
|
400
|
+
# A custom id becomes the job's own session id (`cron-<id>`), so it takes
|
|
401
|
+
# the session-id charset: a `>` would make it a sub-session path.
|
|
402
|
+
if args.id and not SESSION_ID_RE.fullmatch(args.id):
|
|
403
|
+
die(f'invalid --id "{args.id}": letters, digits, _ : - only (it becomes the job\'s session id)')
|
|
396
404
|
job_id = args.id or gen_job_id()
|
|
397
405
|
check_session_rules({
|
|
398
406
|
'id': job_id, 'workspace_path': args.workspace, 'agent_id': args.agent or 'default',
|
|
@@ -451,12 +459,19 @@ def cmd_update(args):
|
|
|
451
459
|
if args.workspace is not None: sets.append(('workspace_path', args.workspace))
|
|
452
460
|
if args.agent is not None: sets.append(('agent_id', args.agent))
|
|
453
461
|
if args.prompt is not None: sets.append(('user_prompt', args.prompt))
|
|
462
|
+
# Recurring vs one-shot, same as the REST PUT: both in one call is an
|
|
463
|
+
# error; one side alone is a mode switch that clears the other (else the
|
|
464
|
+
# runner prefers run_at and its post-fire auto-disable kills the schedule).
|
|
465
|
+
has_schedule = bool(args.schedule and args.schedule.strip())
|
|
466
|
+
run_at_ms = parse_run_at(args.run_at) if args.run_at is not None else None
|
|
467
|
+
if run_at_ms is not None and run_at_ms <= now_ms():
|
|
468
|
+
die('--run-at must be in the future')
|
|
469
|
+
if has_schedule and run_at_ms is not None:
|
|
470
|
+
die('--schedule and --run-at are mutually exclusive')
|
|
454
471
|
if args.schedule is not None: sets.append(('schedule', args.schedule))
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
die('--run-at must be in the future')
|
|
459
|
-
sets.append(('run_at', run_at_ms))
|
|
472
|
+
elif run_at_ms is not None: sets.append(('schedule', ''))
|
|
473
|
+
if args.run_at is not None: sets.append(('run_at', run_at_ms))
|
|
474
|
+
elif has_schedule: sets.append(('run_at', None))
|
|
460
475
|
if args.timezone is not None: sets.append(('timezone', args.timezone))
|
|
461
476
|
if args.timeout_sec is not None:
|
|
462
477
|
# `--timeout-sec ""` clears back to the runner default (NULL),
|
|
@@ -498,6 +513,8 @@ def cmd_update(args):
|
|
|
498
513
|
# Window rules on the merged row (also catches --run-at on a job that
|
|
499
514
|
# still has a window).
|
|
500
515
|
merged = {**cur, **dict(sets)}
|
|
516
|
+
if not (merged.get('schedule') or '').strip() and merged.get('run_at') is None:
|
|
517
|
+
die('--schedule or --run-at required (this update would leave the job with neither)')
|
|
501
518
|
validate_active_window(
|
|
502
519
|
merged.get('run_at'), merged.get('active_from'), merged.get('active_until'),
|
|
503
520
|
new_until=bool(args.active_until and args.active_until.strip()),
|