@sayknow-cli/coding-agent 0.2.7 → 0.3.1
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/CHANGELOG.md +20 -0
- package/dist/types/async/job-manager.d.ts +3 -1
- package/dist/types/cli/daemon-cli.d.ts +25 -0
- package/dist/types/cli/notify-cli.d.ts +25 -0
- package/dist/types/cli/setup-cli.d.ts +20 -1
- package/dist/types/commands/daemon.d.ts +41 -0
- package/dist/types/commands/notify.d.ts +41 -0
- package/dist/types/config/model-profile-activation.d.ts +12 -0
- package/dist/types/config/model-profiles.d.ts +2 -1
- package/dist/types/config/model-registry.d.ts +3 -3
- package/dist/types/config/models-config-schema.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +136 -3
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/daemon/builtin.d.ts +20 -0
- package/dist/types/daemon/control-types.d.ts +57 -0
- package/dist/types/daemon/runtime.d.ts +25 -0
- package/dist/types/extensibility/extensions/types.d.ts +8 -0
- package/dist/types/extensibility/shared-events.d.ts +1 -0
- package/dist/types/i18n/messages/en.d.ts +1 -0
- package/dist/types/lsp/types.d.ts +2 -0
- package/dist/types/modes/components/oauth-selector.d.ts +2 -0
- package/dist/types/modes/components/settings-defs.d.ts +1 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -2
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +10 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +7 -1
- package/dist/types/notifications/attachment-registry.d.ts +17 -0
- package/dist/types/notifications/chat-adapters.d.ts +9 -0
- package/dist/types/notifications/config-commands.d.ts +26 -0
- package/dist/types/notifications/config.d.ts +69 -0
- package/dist/types/notifications/engine.d.ts +59 -0
- package/dist/types/notifications/helpers.d.ts +55 -0
- package/dist/types/notifications/html-format.d.ts +62 -0
- package/dist/types/notifications/index.d.ts +28 -0
- package/dist/types/notifications/managed-daemon.d.ts +48 -0
- package/dist/types/notifications/rate-limit-pool.d.ts +93 -0
- package/dist/types/notifications/telegram-cli.d.ts +19 -0
- package/dist/types/notifications/telegram-daemon-cli.d.ts +11 -0
- package/dist/types/notifications/telegram-daemon-control.d.ts +56 -0
- package/dist/types/notifications/telegram-daemon.d.ts +295 -0
- package/dist/types/notifications/telegram-reference.d.ts +111 -0
- package/dist/types/notifications/threaded-inbound.d.ts +77 -0
- package/dist/types/notifications/threaded-render.d.ts +71 -0
- package/dist/types/notifications/topic-registry.d.ts +67 -0
- package/dist/types/rlm/index.d.ts +12 -0
- package/dist/types/session/agent-session.d.ts +41 -2
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/setup/credential-auto-import.d.ts +63 -0
- package/dist/types/setup/credential-import.d.ts +3 -0
- package/dist/types/setup/host-plugin-setup.d.ts +39 -0
- package/dist/types/skc-runtime/launch-tmux.d.ts +1 -0
- package/dist/types/skc-runtime/ralplan-runtime.d.ts +1 -1
- package/dist/types/skc-runtime/state-writer.d.ts +2 -0
- package/dist/types/skc-runtime/tmux-common.d.ts +3 -0
- package/dist/types/skc-runtime/tmux-sessions.d.ts +2 -0
- package/dist/types/skc-runtime/ultragoal-guard.d.ts +15 -0
- package/dist/types/skc-runtime/ultragoal-runtime.d.ts +14 -0
- package/dist/types/tools/ask-answer-registry.d.ts +13 -0
- package/dist/types/tools/fetch.d.ts +23 -0
- package/dist/types/tools/index.d.ts +19 -0
- package/dist/types/tools/subagent.d.ts +3 -0
- package/dist/types/tools/telegram-send.d.ts +32 -0
- package/dist/types/web/insane/bridge.d.ts +103 -0
- package/dist/types/web/insane/url-guard.d.ts +22 -0
- package/dist/types/web/search/provider.d.ts +18 -1
- package/dist/types/web/search/providers/insane.d.ts +53 -0
- package/dist/types/web/search/providers/text-citations.d.ts +23 -0
- package/dist/types/web/search/types.d.ts +12 -4
- package/package.json +10 -8
- package/scripts/build-binary.ts +3 -0
- package/scripts/verify-insane-vendor.ts +132 -0
- package/src/async/job-manager.ts +5 -1
- package/src/cli/args.ts +1 -1
- package/src/cli/daemon-cli.ts +122 -0
- package/src/cli/fast-help.ts +1 -1
- package/src/cli/notify-cli.ts +421 -0
- package/src/cli/setup-cli.ts +173 -84
- package/src/cli.ts +3 -3
- package/src/commands/daemon.ts +47 -0
- package/src/commands/notify.ts +61 -0
- package/src/commands/setup.ts +11 -1
- package/src/commands/team.ts +1 -1
- package/src/config/model-profile-activation.ts +74 -5
- package/src/config/model-profiles.ts +7 -4
- package/src/config/model-registry.ts +6 -3
- package/src/config/models-config-schema.ts +1 -1
- package/src/config/settings-schema.ts +143 -0
- package/src/coordinator/contract.ts +3 -0
- package/src/coordinator-mcp/server.ts +270 -1
- package/src/daemon/builtin.ts +46 -0
- package/src/daemon/control-types.ts +65 -0
- package/src/daemon/runtime.ts +51 -0
- package/src/defaults/skc/rules/ponytail.md +68 -0
- package/src/defaults/skc/skills/ralplan/SKILL.md +11 -4
- package/src/defaults/skc/skills/ultragoal/SKILL.md +16 -0
- package/src/edit/modes/replace.ts +1 -1
- package/src/extensibility/extensions/runner.ts +4 -0
- package/src/extensibility/extensions/types.ts +8 -0
- package/src/extensibility/shared-events.ts +1 -0
- package/src/goals/tools/goal-tool.ts +11 -2
- package/src/hashline/hash.ts +1 -1
- package/src/i18n/messages/de.ts +1 -0
- package/src/i18n/messages/en.ts +1 -0
- package/src/i18n/messages/es.ts +1 -0
- package/src/i18n/messages/fr.ts +1 -0
- package/src/i18n/messages/ja.ts +1 -0
- package/src/i18n/messages/ko.ts +1 -0
- package/src/i18n/messages/zh.ts +1 -0
- package/src/internal-urls/docs-index.generated.ts +12 -10
- package/src/lsp/config.ts +16 -3
- package/src/lsp/defaults.json +7 -0
- package/src/lsp/types.ts +2 -0
- package/src/main.ts +30 -0
- package/src/modes/acp/acp-event-mapper.ts +1 -0
- package/src/modes/components/hook-editor.ts +7 -2
- package/src/modes/components/oauth-selector.ts +19 -0
- package/src/modes/components/settings-defs.ts +2 -1
- package/src/modes/components/settings-selector.ts +7 -2
- package/src/modes/controllers/event-controller.ts +35 -0
- package/src/modes/controllers/selector-controller.ts +80 -17
- package/src/modes/interactive-mode.ts +52 -4
- package/src/modes/runtime-init.ts +1 -0
- package/src/modes/shared/agent-wire/event-contract.ts +1 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +1 -0
- package/src/modes/shared/agent-wire/event-observation.ts +16 -0
- package/src/modes/shared/agent-wire/unattended-session.ts +22 -0
- package/src/modes/theme/theme.ts +4 -0
- package/src/modes/types.ts +7 -1
- package/src/modes/utils/context-usage.ts +2 -2
- package/src/modes/utils/ui-helpers.ts +23 -0
- package/src/notifications/attachment-registry.ts +23 -0
- package/src/notifications/chat-adapters.ts +147 -0
- package/src/notifications/config-commands.ts +50 -0
- package/src/notifications/config.ts +128 -0
- package/src/notifications/engine.ts +100 -0
- package/src/notifications/helpers.ts +135 -0
- package/src/notifications/html-format.ts +389 -0
- package/src/notifications/index.ts +842 -0
- package/src/notifications/managed-daemon.ts +163 -0
- package/src/notifications/rate-limit-pool.ts +179 -0
- package/src/notifications/telegram-cli.ts +194 -0
- package/src/notifications/telegram-daemon-cli.ts +74 -0
- package/src/notifications/telegram-daemon-control.ts +370 -0
- package/src/notifications/telegram-daemon.ts +1591 -0
- package/src/notifications/telegram-reference.ts +335 -0
- package/src/notifications/threaded-inbound.ts +136 -0
- package/src/notifications/threaded-render.ts +173 -0
- package/src/notifications/topic-registry.ts +133 -0
- package/src/rlm/index.ts +19 -0
- package/src/sdk.ts +16 -0
- package/src/session/agent-session.ts +195 -54
- package/src/session/auth-storage.ts +3 -0
- package/src/session/session-dump-format.ts +43 -2
- package/src/session/session-manager.ts +39 -5
- package/src/setup/credential-auto-import.ts +258 -0
- package/src/setup/credential-import.ts +17 -0
- package/src/setup/hermes/templates/operator-instructions.v1.md +10 -0
- package/src/setup/host-plugin-setup.ts +142 -0
- package/src/skc-runtime/deep-interview-recorder.ts +2 -2
- package/src/skc-runtime/launch-tmux.ts +27 -5
- package/src/skc-runtime/ledger-event-renderer.ts +1 -0
- package/src/skc-runtime/ralplan-runtime.ts +2 -2
- package/src/skc-runtime/state-runtime.ts +18 -10
- package/src/skc-runtime/state-writer.ts +8 -8
- package/src/skc-runtime/tmux-common.ts +8 -0
- package/src/skc-runtime/tmux-sessions.ts +8 -1
- package/src/skc-runtime/ultragoal-guard.ts +57 -2
- package/src/skc-runtime/ultragoal-runtime.ts +105 -19
- package/src/skc-runtime/workflow-manifest.generated.json +56 -2
- package/src/skc-runtime/workflow-manifest.ts +18 -3
- package/src/slash-commands/builtin-registry.ts +4 -1
- package/src/task/executor.ts +5 -1
- package/src/tools/ask-answer-registry.ts +25 -0
- package/src/tools/ask.ts +77 -6
- package/src/tools/fetch.ts +78 -1
- package/src/tools/image-gen.ts +5 -8
- package/src/tools/index.ts +22 -0
- package/src/tools/inspect-image.ts +16 -11
- package/src/tools/subagent-render.ts +7 -0
- package/src/tools/subagent.ts +38 -7
- package/src/tools/telegram-send.ts +137 -0
- package/src/web/insane/bridge.ts +350 -0
- package/src/web/insane/url-guard.ts +155 -0
- package/src/web/search/provider.ts +77 -18
- package/src/web/search/providers/anthropic.ts +70 -3
- package/src/web/search/providers/codex.ts +1 -119
- package/src/web/search/providers/gemini.ts +99 -0
- package/src/web/search/providers/insane.ts +551 -0
- package/src/web/search/providers/openai-compatible.ts +66 -32
- package/src/web/search/providers/text-citations.ts +111 -0
- package/src/web/search/types.ts +13 -2
- package/vendor/insane-search/LICENSE +21 -0
- package/vendor/insane-search/MANIFEST.json +24 -0
- package/vendor/insane-search/engine/__init__.py +23 -0
- package/vendor/insane-search/engine/__main__.py +128 -0
- package/vendor/insane-search/engine/bias_check.py +183 -0
- package/vendor/insane-search/engine/executor.py +254 -0
- package/vendor/insane-search/engine/fetch_chain.py +725 -0
- package/vendor/insane-search/engine/learning.py +175 -0
- package/vendor/insane-search/engine/phase0.py +214 -0
- package/vendor/insane-search/engine/safety.py +91 -0
- package/vendor/insane-search/engine/templates/package.json +11 -0
- package/vendor/insane-search/engine/templates/playwright_mobile_chrome.js +188 -0
- package/vendor/insane-search/engine/templates/playwright_real_chrome.js +243 -0
- package/vendor/insane-search/engine/tests/test_hardening.py +57 -0
- package/vendor/insane-search/engine/tests/test_smoke.py +152 -0
- package/vendor/insane-search/engine/tests/test_u1.py +200 -0
- package/vendor/insane-search/engine/tests/test_u4.py +131 -0
- package/vendor/insane-search/engine/tests/test_u5.py +163 -0
- package/vendor/insane-search/engine/tests/test_u7.py +124 -0
- package/vendor/insane-search/engine/transport.py +211 -0
- package/vendor/insane-search/engine/url_transforms.py +98 -0
- package/vendor/insane-search/engine/validators.py +331 -0
- package/vendor/insane-search/engine/waf_detector.py +214 -0
- package/vendor/insane-search/engine/waf_profiles.yaml +162 -0
|
@@ -369,6 +369,9 @@
|
|
|
369
369
|
{
|
|
370
370
|
"id": "revision"
|
|
371
371
|
},
|
|
372
|
+
{
|
|
373
|
+
"id": "post-interview"
|
|
374
|
+
},
|
|
372
375
|
{
|
|
373
376
|
"id": "adr"
|
|
374
377
|
},
|
|
@@ -401,6 +404,26 @@
|
|
|
401
404
|
"to": "revision",
|
|
402
405
|
"verb": "write-artifact"
|
|
403
406
|
},
|
|
407
|
+
{
|
|
408
|
+
"from": "revision",
|
|
409
|
+
"to": "post-interview",
|
|
410
|
+
"verb": "write-artifact"
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
"from": "critic",
|
|
414
|
+
"to": "post-interview",
|
|
415
|
+
"verb": "write-artifact"
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
"from": "post-interview",
|
|
419
|
+
"to": "revision",
|
|
420
|
+
"verb": "write-artifact"
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
"from": "post-interview",
|
|
424
|
+
"to": "adr",
|
|
425
|
+
"verb": "write-artifact"
|
|
426
|
+
},
|
|
404
427
|
{
|
|
405
428
|
"from": "revision",
|
|
406
429
|
"to": "adr",
|
|
@@ -435,6 +458,11 @@
|
|
|
435
458
|
"from": "adr",
|
|
436
459
|
"to": "handoff",
|
|
437
460
|
"verb": "handoff"
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
"from": "post-interview",
|
|
464
|
+
"to": "handoff",
|
|
465
|
+
"verb": "handoff"
|
|
438
466
|
}
|
|
439
467
|
],
|
|
440
468
|
"typedArgs": [
|
|
@@ -592,6 +620,7 @@
|
|
|
592
620
|
"architect",
|
|
593
621
|
"critic",
|
|
594
622
|
"revision",
|
|
623
|
+
"post-interview",
|
|
595
624
|
"adr",
|
|
596
625
|
"final"
|
|
597
626
|
],
|
|
@@ -1443,7 +1472,8 @@
|
|
|
1443
1472
|
"appliesToVerbs": [
|
|
1444
1473
|
"checkpoint",
|
|
1445
1474
|
"record-review-blockers",
|
|
1446
|
-
"steer"
|
|
1475
|
+
"steer",
|
|
1476
|
+
"classify-blocker"
|
|
1447
1477
|
],
|
|
1448
1478
|
"name": "evidence",
|
|
1449
1479
|
"required": true,
|
|
@@ -1471,6 +1501,25 @@
|
|
|
1471
1501
|
"name": "goal-id",
|
|
1472
1502
|
"type": "string"
|
|
1473
1503
|
},
|
|
1504
|
+
{
|
|
1505
|
+
"appliesToVerbs": [
|
|
1506
|
+
"classify-blocker"
|
|
1507
|
+
],
|
|
1508
|
+
"name": "goal-id",
|
|
1509
|
+
"type": "string"
|
|
1510
|
+
},
|
|
1511
|
+
{
|
|
1512
|
+
"appliesToVerbs": [
|
|
1513
|
+
"classify-blocker"
|
|
1514
|
+
],
|
|
1515
|
+
"enumValues": [
|
|
1516
|
+
"human_blocked",
|
|
1517
|
+
"resolvable"
|
|
1518
|
+
],
|
|
1519
|
+
"name": "classification",
|
|
1520
|
+
"required": true,
|
|
1521
|
+
"type": "enum"
|
|
1522
|
+
},
|
|
1474
1523
|
{
|
|
1475
1524
|
"appliesToVerbs": [
|
|
1476
1525
|
"steer"
|
|
@@ -1570,7 +1619,8 @@
|
|
|
1570
1619
|
"review",
|
|
1571
1620
|
"checkpoint",
|
|
1572
1621
|
"record-review-blockers",
|
|
1573
|
-
"steer"
|
|
1622
|
+
"steer",
|
|
1623
|
+
"classify-blocker"
|
|
1574
1624
|
],
|
|
1575
1625
|
"name": "json",
|
|
1576
1626
|
"type": "boolean"
|
|
@@ -1651,6 +1701,10 @@
|
|
|
1651
1701
|
"name": "steer",
|
|
1652
1702
|
"surface": "command-positional"
|
|
1653
1703
|
},
|
|
1704
|
+
{
|
|
1705
|
+
"name": "classify-blocker",
|
|
1706
|
+
"surface": "command-positional"
|
|
1707
|
+
},
|
|
1654
1708
|
{
|
|
1655
1709
|
"name": "graph",
|
|
1656
1710
|
"planned": true,
|
|
@@ -172,12 +172,16 @@ export const WORKFLOW_MANIFEST: Record<CanonicalSkcWorkflowSkill, SkillManifest>
|
|
|
172
172
|
}),
|
|
173
173
|
ralplan: manifest({
|
|
174
174
|
skill: "ralplan",
|
|
175
|
-
states: ["planner", "architect", "critic", "revision", "adr", "final", "handoff"],
|
|
175
|
+
states: ["planner", "architect", "critic", "revision", "post-interview", "adr", "final", "handoff"],
|
|
176
176
|
terminalStates: ["final", "handoff"],
|
|
177
177
|
transitions: [
|
|
178
178
|
{ from: "planner", to: "architect", verb: "write-artifact" },
|
|
179
179
|
{ from: "architect", to: "critic", verb: "write-artifact" },
|
|
180
180
|
{ from: "critic", to: "revision", verb: "write-artifact" },
|
|
181
|
+
{ from: "revision", to: "post-interview", verb: "write-artifact" },
|
|
182
|
+
{ from: "critic", to: "post-interview", verb: "write-artifact" },
|
|
183
|
+
{ from: "post-interview", to: "revision", verb: "write-artifact" },
|
|
184
|
+
{ from: "post-interview", to: "adr", verb: "write-artifact" },
|
|
181
185
|
{ from: "revision", to: "adr", verb: "write-artifact" },
|
|
182
186
|
{ from: "adr", to: "final", verb: "write-artifact" },
|
|
183
187
|
{ from: "planner", to: "handoff", verb: "handoff" },
|
|
@@ -185,6 +189,7 @@ export const WORKFLOW_MANIFEST: Record<CanonicalSkcWorkflowSkill, SkillManifest>
|
|
|
185
189
|
{ from: "critic", to: "handoff", verb: "handoff" },
|
|
186
190
|
{ from: "revision", to: "handoff", verb: "handoff" },
|
|
187
191
|
{ from: "adr", to: "handoff", verb: "handoff" },
|
|
192
|
+
{ from: "post-interview", to: "handoff", verb: "handoff" },
|
|
188
193
|
],
|
|
189
194
|
verbs: [...stateVerbs(), ...flagVerbs(["kickoff", "write-artifact"]), ...plannedVerbs(PLANNED_ADMIN_VERBS)],
|
|
190
195
|
typedArgs: [
|
|
@@ -196,7 +201,7 @@ export const WORKFLOW_MANIFEST: Record<CanonicalSkcWorkflowSkill, SkillManifest>
|
|
|
196
201
|
{
|
|
197
202
|
name: "stage",
|
|
198
203
|
type: "enum",
|
|
199
|
-
enumValues: ["planner", "architect", "critic", "revision", "adr", "final"],
|
|
204
|
+
enumValues: ["planner", "architect", "critic", "revision", "post-interview", "adr", "final"],
|
|
200
205
|
appliesToVerbs: ["write-artifact"],
|
|
201
206
|
},
|
|
202
207
|
{ name: "stage_n", type: "number", appliesToVerbs: ["write-artifact"] },
|
|
@@ -237,6 +242,7 @@ export const WORKFLOW_MANIFEST: Record<CanonicalSkcWorkflowSkill, SkillManifest>
|
|
|
237
242
|
"review",
|
|
238
243
|
"record-review-blockers",
|
|
239
244
|
"steer",
|
|
245
|
+
"classify-blocker",
|
|
240
246
|
]),
|
|
241
247
|
...plannedVerbs(PLANNED_ADMIN_VERBS),
|
|
242
248
|
],
|
|
@@ -263,11 +269,19 @@ export const WORKFLOW_MANIFEST: Record<CanonicalSkcWorkflowSkill, SkillManifest>
|
|
|
263
269
|
name: "evidence",
|
|
264
270
|
type: "string",
|
|
265
271
|
required: true,
|
|
266
|
-
appliesToVerbs: ["checkpoint", "record-review-blockers", "steer"],
|
|
272
|
+
appliesToVerbs: ["checkpoint", "record-review-blockers", "steer", "classify-blocker"],
|
|
267
273
|
},
|
|
268
274
|
{ name: "skc-goal-json", type: "string", appliesToVerbs: ["checkpoint", "record-review-blockers"] },
|
|
269
275
|
{ name: "quality-gate-json", type: "string", appliesToVerbs: ["checkpoint"] },
|
|
270
276
|
{ name: "goal-id", type: "string", appliesToVerbs: ["steer"] },
|
|
277
|
+
{ name: "goal-id", type: "string", appliesToVerbs: ["classify-blocker"] },
|
|
278
|
+
{
|
|
279
|
+
name: "classification",
|
|
280
|
+
type: "enum",
|
|
281
|
+
enumValues: ["human_blocked", "resolvable"],
|
|
282
|
+
required: true,
|
|
283
|
+
appliesToVerbs: ["classify-blocker"],
|
|
284
|
+
},
|
|
271
285
|
{
|
|
272
286
|
name: "kind",
|
|
273
287
|
type: "enum",
|
|
@@ -307,6 +321,7 @@ export const WORKFLOW_MANIFEST: Record<CanonicalSkcWorkflowSkill, SkillManifest>
|
|
|
307
321
|
"checkpoint",
|
|
308
322
|
"record-review-blockers",
|
|
309
323
|
"steer",
|
|
324
|
+
"classify-blocker",
|
|
310
325
|
],
|
|
311
326
|
},
|
|
312
327
|
{ name: "directive-json", type: "string", appliesToVerbs: ["steer"], planned: true },
|
|
@@ -798,7 +798,10 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
798
798
|
return;
|
|
799
799
|
}
|
|
800
800
|
|
|
801
|
-
runtime.ctx.
|
|
801
|
+
runtime.ctx.showOAuthSelector("login", undefined, {
|
|
802
|
+
allowExternalCredentialDiscovery: true,
|
|
803
|
+
trigger: "bare-login",
|
|
804
|
+
});
|
|
802
805
|
runtime.ctx.editor.setText("");
|
|
803
806
|
},
|
|
804
807
|
},
|
package/src/task/executor.ts
CHANGED
|
@@ -1298,11 +1298,15 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1298
1298
|
requestPause: () => {
|
|
1299
1299
|
pauseRequested = true;
|
|
1300
1300
|
},
|
|
1301
|
-
injectMessage: async (content, deliverAs) => {
|
|
1301
|
+
injectMessage: async (content, deliverAs, opts) => {
|
|
1302
1302
|
if (deliverAs === "nextTurn") {
|
|
1303
1303
|
await session.prompt(content, { attribution: "agent" });
|
|
1304
1304
|
return;
|
|
1305
1305
|
}
|
|
1306
|
+
if (deliverAs === "steer") {
|
|
1307
|
+
const from = opts?.fromAgentId ?? manager.getSubagentRecord(liveSubagentId)?.ownerId ?? "?";
|
|
1308
|
+
session.emitSubagentSteerObservation({ from, to: liveSubagentId, body: content });
|
|
1309
|
+
}
|
|
1306
1310
|
await session.sendUserMessage(content, { deliverAs });
|
|
1307
1311
|
},
|
|
1308
1312
|
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process-wide registry mapping a session id to its active {@link AskAnswerSource}.
|
|
3
|
+
*
|
|
4
|
+
* Decouples the `ask` tool (which reads the source via `AgentSession`) from the
|
|
5
|
+
* notifications extension (which registers one), without threading a new method
|
|
6
|
+
* through the extension/runner/controller wiring. A session has at most one
|
|
7
|
+
* source; registering returns a disposer.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { AskAnswerSource } from "./index";
|
|
11
|
+
|
|
12
|
+
const sources = new Map<string, AskAnswerSource>();
|
|
13
|
+
|
|
14
|
+
/** Register `source` for `sessionId`. Returns a disposer that clears it. */
|
|
15
|
+
export function registerAskAnswerSource(sessionId: string, source: AskAnswerSource): () => void {
|
|
16
|
+
sources.set(sessionId, source);
|
|
17
|
+
return () => {
|
|
18
|
+
if (sources.get(sessionId) === source) sources.delete(sessionId);
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The answer source for `sessionId`, if one is registered. */
|
|
23
|
+
export function getAskAnswerSource(sessionId: string): AskAnswerSource | undefined {
|
|
24
|
+
return sources.get(sessionId);
|
|
25
|
+
}
|
package/src/tools/ask.ts
CHANGED
|
@@ -430,8 +430,18 @@ async function askSingleQuestion(
|
|
|
430
430
|
// If input was dismissed (undefined), keep prior selectedOptions/customInput intact
|
|
431
431
|
}
|
|
432
432
|
} else {
|
|
433
|
-
|
|
434
|
-
|
|
433
|
+
const stripped = stripRecommendedSuffix(choice);
|
|
434
|
+
if (optionLabels.includes(stripped)) {
|
|
435
|
+
selectedOptions = [stripped];
|
|
436
|
+
customInput = undefined;
|
|
437
|
+
} else {
|
|
438
|
+
// A remote answer (e.g. a typed Telegram reply) that is not one of the
|
|
439
|
+
// listed options is the "provide my own" custom input — recorded the same
|
|
440
|
+
// as picking Other and typing it. The local selector can only ever return
|
|
441
|
+
// a listed entry, so this branch is reached only for free-text answers.
|
|
442
|
+
customInput = choice;
|
|
443
|
+
selectedOptions = [];
|
|
444
|
+
}
|
|
435
445
|
}
|
|
436
446
|
if (navigation?.allowForward) {
|
|
437
447
|
return { selectedOptions, customInput, timedOut, navigation: "forward" };
|
|
@@ -551,11 +561,71 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
551
561
|
const ui: UIContext = {
|
|
552
562
|
select: (prompt, options, dialogOptions) => {
|
|
553
563
|
if (!extensionUi) throw new ToolAbortError("Ask tool requires interactive mode");
|
|
554
|
-
|
|
564
|
+
const source = this.session.getAskAnswerSource?.();
|
|
565
|
+
if (!source) return extensionUi.select(prompt, options, dialogOptions);
|
|
566
|
+
// Race the local UI against a remote answer (e.g. a Telegram reply via the
|
|
567
|
+
// notifications SDK) so asks can be answered without RPC mode. When the
|
|
568
|
+
// local UI wins, abort the remote source so it stops waiting and marks the
|
|
569
|
+
// action resolved-locally. First valid answer wins.
|
|
570
|
+
// Race the local UI against a remote answer (e.g. a Telegram reply via the
|
|
571
|
+
// notifications SDK) so asks can be answered without RPC mode. First valid
|
|
572
|
+
// answer wins; the loser is aborted so neither side is left hanging:
|
|
573
|
+
// - local wins -> abort the remote source (marks the action resolved-locally)
|
|
574
|
+
// - remote wins -> abort the local selector so the TUI dialog actually closes
|
|
575
|
+
const remoteController = new AbortController();
|
|
576
|
+
const localController = new AbortController();
|
|
577
|
+
// Propagate an external cancel (the tool's signal) to the local selector too.
|
|
578
|
+
const toolSignal = dialogOptions?.signal;
|
|
579
|
+
if (toolSignal) {
|
|
580
|
+
if (toolSignal.aborted) localController.abort();
|
|
581
|
+
else toolSignal.addEventListener("abort", () => localController.abort(), { once: true });
|
|
582
|
+
}
|
|
583
|
+
const remote = source.awaitAnswer(prompt, options, remoteController.signal).then(answer => {
|
|
584
|
+
// undefined is not a valid remote answer (registration failed, or the local
|
|
585
|
+
// UI already won and aborted us): never settle the race, let the local
|
|
586
|
+
// selector decide instead of cancelling the ask.
|
|
587
|
+
if (answer === undefined) return new Promise<string | undefined>(() => {});
|
|
588
|
+
localController.abort();
|
|
589
|
+
return answer;
|
|
590
|
+
});
|
|
591
|
+
const local = extensionUi
|
|
592
|
+
.select(prompt, options, { ...dialogOptions, signal: localController.signal })
|
|
593
|
+
.then(answer => {
|
|
594
|
+
remoteController.abort();
|
|
595
|
+
return answer;
|
|
596
|
+
});
|
|
597
|
+
// The losing selector may reject when aborted after the race already settled;
|
|
598
|
+
// swallow that so it is not an unhandled rejection (the race result is unaffected).
|
|
599
|
+
void local.catch(() => undefined);
|
|
600
|
+
return Promise.race([local, remote]);
|
|
555
601
|
},
|
|
556
602
|
editor: (title, prefill, dialogOptions, editorOptions) => {
|
|
557
603
|
if (!extensionUi) throw new ToolAbortError("Ask tool requires interactive mode");
|
|
558
|
-
|
|
604
|
+
const source = this.session.getAskAnswerSource?.();
|
|
605
|
+
if (!source) return extensionUi.editor(title, prefill, dialogOptions, editorOptions);
|
|
606
|
+
// Race the local editor against a remote free-text answer so "Other / type
|
|
607
|
+
// your own" custom input can be provided remotely (e.g. a typed Telegram
|
|
608
|
+
// reply) instead of blocking on the local-only editor. Mirrors `select`.
|
|
609
|
+
const remoteController = new AbortController();
|
|
610
|
+
const localController = new AbortController();
|
|
611
|
+
const toolSignal = dialogOptions?.signal;
|
|
612
|
+
if (toolSignal) {
|
|
613
|
+
if (toolSignal.aborted) localController.abort();
|
|
614
|
+
else toolSignal.addEventListener("abort", () => localController.abort(), { once: true });
|
|
615
|
+
}
|
|
616
|
+
const remote = source.awaitAnswer(title, [], remoteController.signal).then(answer => {
|
|
617
|
+
if (answer === undefined) return new Promise<string | undefined>(() => {});
|
|
618
|
+
localController.abort();
|
|
619
|
+
return answer;
|
|
620
|
+
});
|
|
621
|
+
const local = extensionUi
|
|
622
|
+
.editor(title, prefill, { ...(dialogOptions ?? {}), signal: localController.signal }, editorOptions)
|
|
623
|
+
.then(answer => {
|
|
624
|
+
remoteController.abort();
|
|
625
|
+
return answer;
|
|
626
|
+
});
|
|
627
|
+
void local.catch(() => undefined);
|
|
628
|
+
return Promise.race([local, remote]);
|
|
559
629
|
},
|
|
560
630
|
};
|
|
561
631
|
|
|
@@ -605,8 +675,9 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
605
675
|
}
|
|
606
676
|
try {
|
|
607
677
|
const deepInterviewPrompt = formatDeepInterviewSelectorPrompt(q.question);
|
|
678
|
+
const isDeepInterviewQuestion = deepInterviewPrompt !== null || q.deepInterview !== undefined;
|
|
608
679
|
const displayQuestion = deepInterviewPrompt ?? q.question;
|
|
609
|
-
const shouldNumberOptions = isDeepInterviewAskQuestion(q.question);
|
|
680
|
+
const shouldNumberOptions = isDeepInterviewQuestion || isDeepInterviewAskQuestion(q.question);
|
|
610
681
|
const optionLabels = shouldNumberOptions ? numberOptionLabels(rawOptionLabels) : rawOptionLabels;
|
|
611
682
|
const initialSelection =
|
|
612
683
|
shouldNumberOptions && options?.previous
|
|
@@ -630,7 +701,7 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
630
701
|
signal,
|
|
631
702
|
initialSelection,
|
|
632
703
|
navigation: options?.navigation,
|
|
633
|
-
scrollTitleRows:
|
|
704
|
+
scrollTitleRows: isDeepInterviewQuestion ? DEEP_INTERVIEW_SELECTOR_SCROLL_TITLE_ROWS : undefined,
|
|
634
705
|
otherOptionLabel: shouldNumberOptions
|
|
635
706
|
? formatNumberedOptionLabel(OTHER_OPTION, optionLabels.length)
|
|
636
707
|
: undefined,
|
package/src/tools/fetch.ts
CHANGED
|
@@ -16,6 +16,8 @@ import { renderStatusLine } from "../tui";
|
|
|
16
16
|
import { CachedOutputBlock } from "../tui/output-block";
|
|
17
17
|
import { formatDimensionNote, resizeImage } from "../utils/image-resize";
|
|
18
18
|
import { ensureTool } from "../utils/tools-manager";
|
|
19
|
+
import { INSANE_NOTES, tryInsaneFetch } from "../web/insane/bridge";
|
|
20
|
+
import { validatePublicHttpUrlForInsane } from "../web/insane/url-guard";
|
|
19
21
|
import { extractWithParallel, findParallelApiKey, getParallelExtractContent } from "../web/parallel";
|
|
20
22
|
import { specialHandlers } from "../web/scrapers";
|
|
21
23
|
import type { RenderResult } from "../web/scrapers/types";
|
|
@@ -705,6 +707,55 @@ async function handleSpecialUrls(
|
|
|
705
707
|
// Main Render Function
|
|
706
708
|
// =============================================================================
|
|
707
709
|
|
|
710
|
+
/**
|
|
711
|
+
* Opt-in insane-search fallback for blocked / degraded public URL reads.
|
|
712
|
+
*
|
|
713
|
+
* Returns a finalized `method: "insane"` result on success, or null (so the
|
|
714
|
+
* caller continues with its normal degraded behavior). Fail-closed: no note,
|
|
715
|
+
* guard DNS, dependency probe, or subprocess when raw mode or the opt-in
|
|
716
|
+
* setting is off. The public-URL guard runs BEFORE any probe/spawn.
|
|
717
|
+
*/
|
|
718
|
+
export async function tryInsaneFallback(args: {
|
|
719
|
+
url: string;
|
|
720
|
+
finalUrl: string;
|
|
721
|
+
timeout: number;
|
|
722
|
+
raw: boolean;
|
|
723
|
+
settings: Settings;
|
|
724
|
+
signal: AbortSignal | undefined;
|
|
725
|
+
fetchedAt: string;
|
|
726
|
+
notes: string[];
|
|
727
|
+
}): Promise<FetchRenderResult | null> {
|
|
728
|
+
if (args.raw) return null;
|
|
729
|
+
if (args.settings.get("web.insaneFallback") !== true) return null;
|
|
730
|
+
|
|
731
|
+
const target = args.finalUrl || args.url;
|
|
732
|
+
const guard = await validatePublicHttpUrlForInsane(target);
|
|
733
|
+
if (!guard.ok) {
|
|
734
|
+
args.notes.push(INSANE_NOTES.guardBlocked(guard.reason));
|
|
735
|
+
return null;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
const result = await tryInsaneFetch(guard.url.toString(), {
|
|
739
|
+
timeoutMs: args.timeout * 1000,
|
|
740
|
+
signal: args.signal,
|
|
741
|
+
});
|
|
742
|
+
if (result.ok) {
|
|
743
|
+
const output = finalizeOutput(result.content);
|
|
744
|
+
return {
|
|
745
|
+
url: args.url,
|
|
746
|
+
finalUrl: target,
|
|
747
|
+
contentType: "text/markdown",
|
|
748
|
+
method: "insane",
|
|
749
|
+
content: output.content,
|
|
750
|
+
fetchedAt: args.fetchedAt,
|
|
751
|
+
truncated: output.truncated,
|
|
752
|
+
notes: [...args.notes, ...result.notes],
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
for (const note of result.notes) args.notes.push(note);
|
|
756
|
+
return null;
|
|
757
|
+
}
|
|
758
|
+
|
|
708
759
|
/**
|
|
709
760
|
* Main render function implementing the full pipeline
|
|
710
761
|
*/
|
|
@@ -751,6 +802,19 @@ async function renderUrl(
|
|
|
751
802
|
throw new ToolAbortError();
|
|
752
803
|
}
|
|
753
804
|
if (!response.ok) {
|
|
805
|
+
const failureNote = response.status ? `Failed to fetch URL (HTTP ${response.status})` : "Failed to fetch URL";
|
|
806
|
+
notes.push(failureNote);
|
|
807
|
+
const insane = await tryInsaneFallback({
|
|
808
|
+
url,
|
|
809
|
+
finalUrl: response.finalUrl || url,
|
|
810
|
+
timeout,
|
|
811
|
+
raw,
|
|
812
|
+
settings,
|
|
813
|
+
signal,
|
|
814
|
+
fetchedAt,
|
|
815
|
+
notes,
|
|
816
|
+
});
|
|
817
|
+
if (insane) return insane;
|
|
754
818
|
return {
|
|
755
819
|
url,
|
|
756
820
|
finalUrl: response.finalUrl || url,
|
|
@@ -759,7 +823,7 @@ async function renderUrl(
|
|
|
759
823
|
content: "",
|
|
760
824
|
fetchedAt,
|
|
761
825
|
truncated: false,
|
|
762
|
-
notes
|
|
826
|
+
notes,
|
|
763
827
|
};
|
|
764
828
|
}
|
|
765
829
|
|
|
@@ -1062,6 +1126,8 @@ async function renderUrl(
|
|
|
1062
1126
|
const htmlResult = await renderHtmlToText(finalUrl, rawContent, timeout, settings, signal, storage);
|
|
1063
1127
|
if (!htmlResult.ok) {
|
|
1064
1128
|
notes.push("html rendering failed (lynx/html2text unavailable)");
|
|
1129
|
+
const insane = await tryInsaneFallback({ url, finalUrl, timeout, raw, settings, signal, fetchedAt, notes });
|
|
1130
|
+
if (insane) return insane;
|
|
1065
1131
|
const output = finalizeOutput(rawContent);
|
|
1066
1132
|
return {
|
|
1067
1133
|
url,
|
|
@@ -1122,6 +1188,17 @@ async function renderUrl(
|
|
|
1122
1188
|
};
|
|
1123
1189
|
}
|
|
1124
1190
|
|
|
1191
|
+
const insaneLowQuality = await tryInsaneFallback({
|
|
1192
|
+
url,
|
|
1193
|
+
finalUrl,
|
|
1194
|
+
timeout,
|
|
1195
|
+
raw,
|
|
1196
|
+
settings,
|
|
1197
|
+
signal,
|
|
1198
|
+
fetchedAt,
|
|
1199
|
+
notes,
|
|
1200
|
+
});
|
|
1201
|
+
if (insaneLowQuality) return insaneLowQuality;
|
|
1125
1202
|
notes.push("Page appears to require JavaScript or is mostly navigation");
|
|
1126
1203
|
}
|
|
1127
1204
|
|
package/src/tools/image-gen.ts
CHANGED
|
@@ -472,20 +472,17 @@ async function findImageApiKey(
|
|
|
472
472
|
const openAI = await findOpenAIHostedImageCredentials(modelRegistry, activeModel, sessionId);
|
|
473
473
|
if (openAI) return openAI;
|
|
474
474
|
// Fall through to auto-detect if preferred provider key not found.
|
|
475
|
-
} else if (preferredImageProvider === "antigravity"
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
// Fall through to auto-detect if preferred provider key not found.
|
|
475
|
+
} else if (preferredImageProvider === "antigravity") {
|
|
476
|
+
if (!modelRegistry) return null;
|
|
477
|
+
return await findAntigravityCredentials(modelRegistry, sessionId);
|
|
479
478
|
} else if (preferredImageProvider === "gemini") {
|
|
480
479
|
const geminiKey = getEnvApiKey("google");
|
|
481
480
|
if (geminiKey) return { provider: "gemini", apiKey: geminiKey };
|
|
482
481
|
const googleKey = $env.GOOGLE_API_KEY;
|
|
483
|
-
|
|
484
|
-
// Fall through to auto-detect if preferred provider key not found.
|
|
482
|
+
return googleKey ? { provider: "gemini", apiKey: googleKey } : null;
|
|
485
483
|
} else if (preferredImageProvider === "openrouter") {
|
|
486
484
|
const openRouterKey = getEnvApiKey("openrouter");
|
|
487
|
-
|
|
488
|
-
// Fall through to auto-detect if preferred provider key not found.
|
|
485
|
+
return openRouterKey ? { provider: "openrouter", apiKey: openRouterKey } : null;
|
|
489
486
|
}
|
|
490
487
|
|
|
491
488
|
// Auto-detect: GPT hosted image generation, then Antigravity, OpenRouter, Gemini.
|
package/src/tools/index.ts
CHANGED
|
@@ -58,6 +58,7 @@ import { SearchToolBm25Tool } from "./search-tool-bm25";
|
|
|
58
58
|
import { SkillTool } from "./skill";
|
|
59
59
|
import { loadSshTool } from "./ssh";
|
|
60
60
|
import { SubagentTool } from "./subagent";
|
|
61
|
+
import { TelegramSendTool } from "./telegram-send";
|
|
61
62
|
import { type TodoPhase, TodoWriteTool } from "./todo-write";
|
|
62
63
|
import { WriteTool } from "./write";
|
|
63
64
|
import { YieldTool } from "./yield";
|
|
@@ -96,6 +97,7 @@ export * from "./search-tool-bm25";
|
|
|
96
97
|
export * from "./skill";
|
|
97
98
|
export * from "./ssh";
|
|
98
99
|
export * from "./subagent";
|
|
100
|
+
export * from "./telegram-send";
|
|
99
101
|
export * from "./todo-write";
|
|
100
102
|
export * from "./vim";
|
|
101
103
|
export * from "./write";
|
|
@@ -118,6 +120,18 @@ export type {
|
|
|
118
120
|
DiscoverableToolSource,
|
|
119
121
|
} from "../tool-discovery/tool-index";
|
|
120
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Source of remote answers for interactive asks (e.g. a Telegram reply routed
|
|
125
|
+
* through the notifications SDK). Lets a pending ask resolve without RPC mode.
|
|
126
|
+
*/
|
|
127
|
+
export interface AskAnswerSource {
|
|
128
|
+
/**
|
|
129
|
+
* Race a remote answer against the local UI for one question. Resolves with the
|
|
130
|
+
* chosen option label or free-text answer, or `undefined` to defer to local UI.
|
|
131
|
+
*/
|
|
132
|
+
awaitAnswer(question: string, options: string[], signal?: AbortSignal): Promise<string | undefined>;
|
|
133
|
+
}
|
|
134
|
+
|
|
121
135
|
/** Session context for tool factories */
|
|
122
136
|
export interface ToolSession {
|
|
123
137
|
/** Current working directory */
|
|
@@ -214,6 +228,13 @@ export interface ToolSession {
|
|
|
214
228
|
getGoalModeState?: () => GoalModeState | undefined;
|
|
215
229
|
/** Unattended workflow-gate emitter (present only when unattended mode is negotiated). */
|
|
216
230
|
getWorkflowGateEmitter?: () => WorkflowGateEmitter | undefined;
|
|
231
|
+
/**
|
|
232
|
+
* Optional remote answer source for interactive asks. When present, the ask
|
|
233
|
+
* tool races the local UI selection against a remote answer (e.g. a Telegram
|
|
234
|
+
* reply via the notifications SDK) so asks can be answered without RPC mode.
|
|
235
|
+
* No-op when undefined: the ask path behaves exactly as before.
|
|
236
|
+
*/
|
|
237
|
+
getAskAnswerSource?: () => AskAnswerSource | undefined;
|
|
217
238
|
/** Optional per-session restriction for goal tool operations. */
|
|
218
239
|
goalToolAllowedOps?: readonly ("create" | "get" | "complete" | "resume" | "drop" | "pause")[];
|
|
219
240
|
/** Goal runtime for the active agent session. */
|
|
@@ -383,6 +404,7 @@ export const BUILTIN_TOOLS: Record<string, ToolFactory> = {
|
|
|
383
404
|
todo_write: s => new TodoWriteTool(s),
|
|
384
405
|
web_search: s => new WebSearchTool(s),
|
|
385
406
|
search_tool_bm25: SearchToolBm25Tool.createIf,
|
|
407
|
+
telegram_send: TelegramSendTool.createIf,
|
|
386
408
|
write: s => new WriteTool(s),
|
|
387
409
|
skill: SkillTool.createIf,
|
|
388
410
|
goal: s => new GoalTool(s),
|
|
@@ -78,21 +78,26 @@ export class InspectImageTool implements AgentTool<typeof inspectImageSchema, In
|
|
|
78
78
|
};
|
|
79
79
|
|
|
80
80
|
const activeModelPattern = this.session.getActiveModelString?.() ?? this.session.getModelString?.();
|
|
81
|
-
|
|
81
|
+
const configuredVisionPattern = this.session.settings.getModelRole("vision")?.trim();
|
|
82
|
+
const configuredVisionModel = configuredVisionPattern ? resolvePattern("pi/vision") : undefined;
|
|
83
|
+
if (configuredVisionPattern && !configuredVisionModel) {
|
|
84
|
+
throw new ToolError(
|
|
85
|
+
`Configured modelRoles.vision (${configuredVisionPattern}) did not resolve to an available model. Configure modelRoles.vision with a vision-capable model.`,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
const model = configuredVisionModel ?? resolvePattern("pi/default") ?? resolvePattern(activeModelPattern);
|
|
82
89
|
if (!model) {
|
|
83
|
-
throw new ToolError(
|
|
90
|
+
throw new ToolError(
|
|
91
|
+
"Unable to resolve a model for inspect_image. Configure modelRoles.vision with a vision-capable model or select a vision-capable active/default model.",
|
|
92
|
+
);
|
|
84
93
|
}
|
|
85
94
|
|
|
86
|
-
// inspect_image requires image input
|
|
87
|
-
//
|
|
95
|
+
// inspect_image requires image input. A text-only selected model must be
|
|
96
|
+
// paired with an explicit vision role so the model/cost boundary is visible.
|
|
88
97
|
if (!model.input.includes("image")) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
`Resolved model ${model.provider}/${model.id} does not support image input, and no vision-capable model is available. Configure a vision-capable model.`,
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
model = visionModel;
|
|
98
|
+
throw new ToolError(
|
|
99
|
+
`Resolved model ${model.provider}/${model.id} does not support image input. Configure modelRoles.vision with a vision-capable model.`,
|
|
100
|
+
);
|
|
96
101
|
}
|
|
97
102
|
|
|
98
103
|
const apiKey = await modelRegistry.getApiKey(model);
|
|
@@ -160,6 +160,13 @@ function renderSubagentSnapshotBody(snapshot: SubagentSnapshot, expanded: boolea
|
|
|
160
160
|
lines.push(` ${theme.fg("dim", "Assignment:")}`);
|
|
161
161
|
for (const al of snapshot.assignment.split("\n")) lines.push(` ${theme.fg("toolOutput", replaceTabs(al))}`);
|
|
162
162
|
}
|
|
163
|
+
if (snapshot.steerMessage) {
|
|
164
|
+
lines.push(` ${theme.fg("accent", `Steer (${snapshot.steerState ?? "queued"})`)}`);
|
|
165
|
+
const maxLines = expanded ? PREVIEW_LINES_EXPANDED : PREVIEW_LINES_COLLAPSED;
|
|
166
|
+
for (const pl of getPreviewLines(snapshot.steerMessage, maxLines, PREVIEW_LINE_WIDTH, Ellipsis.Unicode)) {
|
|
167
|
+
lines.push(` ${theme.fg("toolOutput", replaceTabs(pl))}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
163
170
|
|
|
164
171
|
// Defense in depth: the producer only attaches `progress` when a live producer
|
|
165
172
|
// exists (subagent.ts #liveProgressFields), but the renderer also honors an
|