@songsid/agend 2.1.5 → 2.1.6-beta.10
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/dist/agent-endpoint.d.ts +18 -0
- package/dist/agent-endpoint.js +53 -2
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/apply-job.d.ts +114 -0
- package/dist/apply-job.js +214 -0
- package/dist/apply-job.js.map +1 -0
- package/dist/backend/claude-code.d.ts +25 -0
- package/dist/backend/claude-code.js +127 -0
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex.d.ts +57 -0
- package/dist/backend/codex.js +183 -0
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/credential-profile.d.ts +159 -0
- package/dist/backend/credential-profile.js +340 -0
- package/dist/backend/credential-profile.js.map +1 -0
- package/dist/backend/factory.js +4 -1
- package/dist/backend/factory.js.map +1 -1
- package/dist/backend/kiro-auth-store.d.ts +25 -0
- package/dist/backend/kiro-auth-store.js +61 -0
- package/dist/backend/kiro-auth-store.js.map +1 -0
- package/dist/backend/kiro.d.ts +8 -0
- package/dist/backend/kiro.js +31 -2
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/muse.d.ts +90 -0
- package/dist/backend/muse.js +437 -0
- package/dist/backend/muse.js.map +1 -0
- package/dist/backend/types.d.ts +54 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/channel/access-manager.d.ts +20 -0
- package/dist/channel/access-manager.js +25 -0
- package/dist/channel/access-manager.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +17 -0
- package/dist/channel/adapters/discord.js +72 -11
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +6 -0
- package/dist/channel/adapters/telegram.js +36 -0
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/markdown-chunk.d.ts +8 -3
- package/dist/channel/markdown-chunk.js +12 -3
- package/dist/channel/markdown-chunk.js.map +1 -1
- package/dist/channel/mcp-server.js +9 -12
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/channel/mcp-tools.d.ts +6 -2
- package/dist/channel/mcp-tools.js +6 -36
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/tool-router.js +32 -3
- package/dist/channel/tool-router.js.map +1 -1
- package/dist/channel/types.d.ts +18 -0
- package/dist/cli.js +113 -0
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.js +45 -5
- package/dist/config-validator.js.map +1 -1
- package/dist/connection-secrets.d.ts +96 -0
- package/dist/connection-secrets.js +25 -0
- package/dist/connection-secrets.js.map +1 -0
- package/dist/daemon.d.ts +76 -0
- package/dist/daemon.js +489 -103
- package/dist/daemon.js.map +1 -1
- package/dist/event-log.d.ts +9 -0
- package/dist/event-log.js +21 -0
- package/dist/event-log.js.map +1 -1
- package/dist/fleet-level-config.d.ts +42 -0
- package/dist/fleet-level-config.js +78 -0
- package/dist/fleet-level-config.js.map +1 -0
- package/dist/fleet-lock.d.ts +21 -0
- package/dist/fleet-lock.js +42 -8
- package/dist/fleet-lock.js.map +1 -1
- package/dist/fleet-manager.d.ts +425 -2
- package/dist/fleet-manager.js +2055 -182
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +145 -0
- package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +11 -1
- package/dist/instance-config-impact.d.ts +55 -0
- package/dist/instance-config-impact.js +152 -0
- package/dist/instance-config-impact.js.map +1 -0
- package/dist/instance-lifecycle.js +8 -4
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/locale.js +24 -0
- package/dist/locale.js.map +1 -1
- package/dist/logger.js +42 -25
- package/dist/logger.js.map +1 -1
- package/dist/muse-usage-relay.d.ts +72 -0
- package/dist/muse-usage-relay.js +419 -0
- package/dist/muse-usage-relay.js.map +1 -0
- package/dist/outbound-handlers.d.ts +5 -1
- package/dist/outbound-handlers.js +199 -1
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +7 -5
- package/dist/outbound-schemas.js +3 -2
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/provider-probe.d.ts +48 -0
- package/dist/provider-probe.js +121 -0
- package/dist/provider-probe.js.map +1 -0
- package/dist/provider-secret-registry.d.ts +98 -0
- package/dist/provider-secret-registry.js +334 -0
- package/dist/provider-secret-registry.js.map +1 -0
- package/dist/quickstart-api.d.ts +164 -0
- package/dist/quickstart-api.js +351 -0
- package/dist/quickstart-api.js.map +1 -0
- package/dist/quickstart.js +23 -50
- package/dist/quickstart.js.map +1 -1
- package/dist/scheduler/db.js +17 -5
- package/dist/scheduler/db.js.map +1 -1
- package/dist/scheduler/db.test.js +34 -1
- package/dist/scheduler/db.test.js.map +1 -1
- package/dist/scheduler/types.d.ts +4 -0
- package/dist/scheduler/types.js.map +1 -1
- package/dist/secret-store.d.ts +25 -0
- package/dist/secret-store.js +165 -0
- package/dist/secret-store.js.map +1 -0
- package/dist/self-restart-limit.d.ts +21 -0
- package/dist/self-restart-limit.js +129 -0
- package/dist/self-restart-limit.js.map +1 -0
- package/dist/settings-api.d.ts +116 -0
- package/dist/settings-api.js +530 -5
- package/dist/settings-api.js.map +1 -1
- package/dist/setup-auth.d.ts +113 -0
- package/dist/setup-auth.js +181 -0
- package/dist/setup-auth.js.map +1 -0
- package/dist/setup-form.d.ts +18 -0
- package/dist/setup-form.js +315 -0
- package/dist/setup-form.js.map +1 -0
- package/dist/setup-host.d.ts +162 -0
- package/dist/setup-host.js +496 -0
- package/dist/setup-host.js.map +1 -0
- package/dist/setup-marker.d.ts +8 -0
- package/dist/setup-marker.js +39 -0
- package/dist/setup-marker.js.map +1 -0
- package/dist/setup-tunnel-consent.d.ts +53 -0
- package/dist/setup-tunnel-consent.js +82 -0
- package/dist/setup-tunnel-consent.js.map +1 -0
- package/dist/setup-wizard.js +4 -0
- package/dist/setup-wizard.js.map +1 -1
- package/dist/steer-capability.js +4 -1
- package/dist/steer-capability.js.map +1 -1
- package/dist/tips.js +1 -1
- package/dist/tips.js.map +1 -1
- package/dist/tmux-manager.js +29 -4
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tool-permissions-notice.d.ts +38 -0
- package/dist/tool-permissions-notice.js +92 -0
- package/dist/tool-permissions-notice.js.map +1 -0
- package/dist/tool-permissions.d.ts +120 -0
- package/dist/tool-permissions.js +277 -0
- package/dist/tool-permissions.js.map +1 -0
- package/dist/topic-commands.js +1 -0
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-sources.d.ts +12 -1
- package/dist/transcript-sources.js +19 -3
- package/dist/transcript-sources.js.map +1 -1
- package/dist/tunnel/cloudflared.d.ts +71 -0
- package/dist/tunnel/cloudflared.js +447 -0
- package/dist/tunnel/cloudflared.js.map +1 -0
- package/dist/tunnel/lease.d.ts +75 -0
- package/dist/tunnel/lease.js +209 -0
- package/dist/tunnel/lease.js.map +1 -0
- package/dist/tunnel/manager.d.ts +56 -0
- package/dist/tunnel/manager.js +167 -0
- package/dist/tunnel/manager.js.map +1 -0
- package/dist/tunnel/types.d.ts +119 -0
- package/dist/tunnel/types.js +33 -0
- package/dist/tunnel/types.js.map +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/ui/dashboard.html +4 -3
- package/dist/ui/settings.html +808 -156
- package/dist/ui/view.html +2 -2
- package/dist/usage/i18n-keys.d.ts +1 -1
- package/dist/usage/i18n-keys.js +1 -1
- package/dist/usage/i18n-keys.js.map +1 -1
- package/dist/usage/providers.d.ts +23 -14
- package/dist/usage/providers.js +220 -58
- package/dist/usage/providers.js.map +1 -1
- package/dist/usage/usage-api.d.ts +13 -0
- package/dist/usage/usage-api.js +69 -1
- package/dist/usage/usage-api.js.map +1 -1
- package/dist/web-api.js +8 -5
- package/dist/web-api.js.map +1 -1
- package/dist/web-auth.d.ts +75 -0
- package/dist/web-auth.js +208 -2
- package/dist/web-auth.js.map +1 -1
- package/dist/web-terminal.d.ts +30 -1
- package/dist/web-terminal.js +86 -3
- package/dist/web-terminal.js.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: credential-profiles
|
|
3
|
+
description: Run agents on more than one subscription of the same backend. Use when someone asks for a worker on a different account, wants an agent moved to another subscription, or asks which subscription an agent is using. Currently kiro-cli only.
|
|
4
|
+
roles: [general]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Credential Profiles
|
|
8
|
+
|
|
9
|
+
One AgEnD fleet normally shares one login per backend. A **credential profile**
|
|
10
|
+
is a named, separate login for the same backend, so two agents can run on two
|
|
11
|
+
subscriptions at once.
|
|
12
|
+
|
|
13
|
+
`kiro-cli` and `codex` have profiles today. For any other backend, say so rather
|
|
14
|
+
than setting an option that will be ignored.
|
|
15
|
+
|
|
16
|
+
**The two behave differently when you switch, and it is not a setting.** Kiro
|
|
17
|
+
keeps its conversations inside the same database as its login, so a different
|
|
18
|
+
subscription is a different set of them: switching is a new conversation. Codex
|
|
19
|
+
keeps its login in one file beside conversation stores that carry no account, so
|
|
20
|
+
switching leaves the history where it is and both subscriptions see it. Say
|
|
21
|
+
which one you are talking about.
|
|
22
|
+
|
|
23
|
+
## Is a profile already set up?
|
|
24
|
+
|
|
25
|
+
A profile exists once someone has logged into it on the host. AgEnD cannot log
|
|
26
|
+
in for them: the login is a browser device flow.
|
|
27
|
+
|
|
28
|
+
**A switch to a profile that has never been logged in is refused**, and the
|
|
29
|
+
error carries the exact command to run. This is deliberate: kiro-cli does not
|
|
30
|
+
start a signed-out session, it stops at "let's get you signed in!" and waits for
|
|
31
|
+
a keypress, so the agent would sit on a login screen instead of working. Relay
|
|
32
|
+
the command, do not try to work around the refusal.
|
|
33
|
+
|
|
34
|
+
Ask the operator to run this on the host once per subscription:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
XDG_DATA_HOME="${AGEND_HOME:-~/.agend}/credential-profiles/kiro-cli/<profile>" kiro-cli login
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
For codex the variable and the path are different:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
CODEX_HOME="${AGEND_HOME:-~/.agend}/credential-profiles/codex/<profile>" codex login
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Create a worker on another subscription
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
create_instance(
|
|
50
|
+
name: "research-b",
|
|
51
|
+
working_directory: "/home/you/projects/app",
|
|
52
|
+
backend: "kiro-cli",
|
|
53
|
+
backend_options: { "kiro-cli": { "credential_profile": "personal" } },
|
|
54
|
+
)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The profile name is yours to choose — `work`, `personal`, `team-b`. Reuse the
|
|
58
|
+
same name for every agent that should share that subscription.
|
|
59
|
+
|
|
60
|
+
## Move an existing agent to another subscription
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
update_instance_config(
|
|
64
|
+
name: "research-a",
|
|
65
|
+
config: { backend_options: { "kiro-cli": { "credential_profile": "personal" } } },
|
|
66
|
+
)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The credentials are read when the CLI starts, so **the instance is restarted
|
|
70
|
+
for you** and the reply says `restarted: true`.
|
|
71
|
+
|
|
72
|
+
**For kiro**, a switch is always a **new conversation**, and the reply says so
|
|
73
|
+
(`conversation_carried_over: false`). kiro keeps its conversations in the same
|
|
74
|
+
database as its login, so a different subscription has a different set of them
|
|
75
|
+
and there is nothing to resume — AgEnD does not even try. What it does instead
|
|
76
|
+
is hand the new session a summary of what the old one was doing
|
|
77
|
+
(`handover_chars` says how much), so the agent can pick the work up without the
|
|
78
|
+
transcript. Tell the user plainly: the agent restarted on the other
|
|
79
|
+
subscription, it knows what it was doing, it cannot quote what was said.
|
|
80
|
+
|
|
81
|
+
**For codex** the reply says `conversation_carried_over: true` and carries a
|
|
82
|
+
`conversation_note`. Relay it as what it is — a statement about where the
|
|
83
|
+
history lives, not a promise about what happens next. The accurate sentence is
|
|
84
|
+
"codex keeps its history outside the login, so both subscriptions share it".
|
|
85
|
+
**Do not promise the agent will pick the thread back up**: whether codex reopens
|
|
86
|
+
a conversation recorded under a different account has not been tested yet. If it
|
|
87
|
+
does not, the agent starts a fresh conversation and carries on — nothing breaks,
|
|
88
|
+
it just does not continue.
|
|
89
|
+
|
|
90
|
+
`backend_options` merges per backend, so setting a kiro option leaves a codex
|
|
91
|
+
option on the same instance alone.
|
|
92
|
+
|
|
93
|
+
## Move an agent back to the default login
|
|
94
|
+
|
|
95
|
+
A merge cannot remove a key, so send `null` for it:
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
update_instance_config(
|
|
99
|
+
name: "research-a",
|
|
100
|
+
config: { backend_options: { "kiro-cli": { "credential_profile": null } } },
|
|
101
|
+
)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The agent goes back to the login the host uses with no profile set, and is
|
|
105
|
+
restarted for the same reason as any other switch.
|
|
106
|
+
|
|
107
|
+
## Answer "which subscription is this agent on?"
|
|
108
|
+
|
|
109
|
+
Read it from the instance config (`get_fleet_config` or `describe_instance`):
|
|
110
|
+
`backend_options["kiro-cli"].credential_profile`. **No profile means the shared
|
|
111
|
+
login** — the one `kiro-cli login` uses on the host with no environment set.
|
|
112
|
+
Say "the default login", not "no subscription".
|
|
113
|
+
|
|
114
|
+
## Answer "how much is left on each subscription?"
|
|
115
|
+
|
|
116
|
+
`get_usage` returns one row per subscription: `Kiro (work)` and
|
|
117
|
+
`Kiro (personal)` rather than a single `Kiro`. Read them out separately — they
|
|
118
|
+
are two quotas, and adding them together would give a number that is true of
|
|
119
|
+
neither. An agent with no profile appears under the plain `Kiro` row.
|
|
120
|
+
|
|
121
|
+
A profile that is configured but has never been logged in still gets a row,
|
|
122
|
+
reading **Signed out — run `kiro-cli` to log in**. That is the reminder to go
|
|
123
|
+
and log it in, so report it as such rather than as a missing subscription.
|
|
124
|
+
|
|
125
|
+
## What to tell the user
|
|
126
|
+
|
|
127
|
+
- Two agents with the same profile name share one login and one quota.
|
|
128
|
+
- An agent with no profile uses the fleet's default login, exactly as before.
|
|
129
|
+
- Quotas only add up if the profiles are genuinely different billing accounts.
|
|
130
|
+
Two profiles logged into the same account do not double anything, and it is
|
|
131
|
+
worth saying so before someone sets this up expecting more headroom.
|
|
132
|
+
- Switching a profile does not carry the conversation across; the agent gets a
|
|
133
|
+
summary of the work, not the transcript. Say anything that must survive
|
|
134
|
+
verbatim in the channel before switching.
|
|
135
|
+
|
|
136
|
+
## Do not
|
|
137
|
+
|
|
138
|
+
- Do not invent a profile that has never been logged in and report success —
|
|
139
|
+
the switch is refused, and the refusal tells you what to relay.
|
|
140
|
+
- Do not set `credential_profile` for a backend other than `kiro-cli` or
|
|
141
|
+
`codex`; it is ignored, and the config validator warns about it.
|
|
142
|
+
- Do not tell a codex user their conversation will definitely continue across a
|
|
143
|
+
switch. Say the history is shared; that part is verified.
|
|
144
|
+
- Do not use a profile name with slashes or spaces — it becomes a directory
|
|
145
|
+
name and is rejected.
|
|
@@ -10,7 +10,17 @@ roles: [worker]
|
|
|
10
10
|
|
|
11
11
|
- Treat the assignment as the full scope unless the sender explicitly expands it.
|
|
12
12
|
- Work without acknowledgment-only messages. Silence means the task is in progress.
|
|
13
|
-
-
|
|
13
|
+
- Fleet orchestration is not yours: you do not have `create_instance`,
|
|
14
|
+
`delete_instance`, `restart_instance`, `deploy_template`, the team tools, or
|
|
15
|
+
the tools that create, change or delete schedules. Calling one is refused
|
|
16
|
+
with an explanation rather than silently ignored. If the work needs more
|
|
17
|
+
capacity or another agent restarted, say so with `report_result` and let the
|
|
18
|
+
coordinator decide.
|
|
19
|
+
- You can still *look*: `list_schedules`, `list_deployments`, `list_instances`
|
|
20
|
+
and the other read-only queries are yours, so check before asking.
|
|
21
|
+
- You *can* `delegate_task`, because it only hands work to an instance that
|
|
22
|
+
already exists. Prefer reporting back over delegating sideways unless the
|
|
23
|
+
assignment said otherwise — delegation belongs to whoever is coordinating.
|
|
14
24
|
|
|
15
25
|
## Request missing information
|
|
16
26
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { InstanceConfig } from "./types.js";
|
|
2
|
+
/** ⚡ applied to the running agent / 🔄 restart the agent / 🔄🔄 restart AgEnD. */
|
|
3
|
+
export type ConfigImpact = "now" | "instance" | "fleet";
|
|
4
|
+
/** Cheapest first. A batch of edits costs as much as its worst member. */
|
|
5
|
+
export declare const IMPACT_ORDER: readonly ConfigImpact[];
|
|
6
|
+
/**
|
|
7
|
+
* Instance settings a live daemon can take without a restart.
|
|
8
|
+
*
|
|
9
|
+
* Adding a key here is only half the change: `Daemon.applyConfigUpdate` has to
|
|
10
|
+
* learn it too, or the setting is written to disk, reported as applied, and
|
|
11
|
+
* silently ignored by the running agent. `tests/hot-cold-parity.test.ts` fails
|
|
12
|
+
* when the two drift.
|
|
13
|
+
*/
|
|
14
|
+
export declare const HOT_INSTANCE_CONFIG_KEYS: ReadonlySet<keyof InstanceConfig>;
|
|
15
|
+
/**
|
|
16
|
+
* The hot keys a ClassicBot channel edit can use.
|
|
17
|
+
*
|
|
18
|
+
* Narrower than the fleet-wide set because a classic channel's hot path carries
|
|
19
|
+
* exactly what `FleetManager.classicBehaviorUpdate()` builds; a key that is hot
|
|
20
|
+
* for a fleet instance but absent from that payload (auto_pause_after) would be
|
|
21
|
+
* dropped on the way. Derived, not re-listed: dropping a key from the hot set
|
|
22
|
+
* drops it here too.
|
|
23
|
+
*/
|
|
24
|
+
export declare const CLASSIC_HOT_CONFIG_KEYS: ReadonlySet<string>;
|
|
25
|
+
/** Split a config into what a live daemon can take and what needs a restart. */
|
|
26
|
+
export declare function splitHotColdConfig(config: InstanceConfig): {
|
|
27
|
+
hot: Partial<InstanceConfig>;
|
|
28
|
+
cold: Partial<InstanceConfig>;
|
|
29
|
+
};
|
|
30
|
+
/** The complete hot snapshot sent over IPC; absent values are explicit nulls so
|
|
31
|
+
* the daemon removes them rather than keeping a stale override. */
|
|
32
|
+
export declare function hotConfigUpdate(config: InstanceConfig): Record<string, unknown>;
|
|
33
|
+
/**
|
|
34
|
+
* What applying `next` to a running instance costs.
|
|
35
|
+
*
|
|
36
|
+
* The one comparison `reconcileInstances` makes when it decides between an IPC
|
|
37
|
+
* hot update and a stop/start — shared so the apply job's plan says what the
|
|
38
|
+
* reconcile will actually do rather than holding a second opinion about it.
|
|
39
|
+
*/
|
|
40
|
+
export declare function classifyInstanceChange(runtime: InstanceConfig, next: InstanceConfig): "restart" | "hot" | "none";
|
|
41
|
+
export declare function instanceFieldImpact(key: string): ConfigImpact;
|
|
42
|
+
export declare function classicFieldImpact(key: string): ConfigImpact;
|
|
43
|
+
/**
|
|
44
|
+
* Impact of one staged edit: hot only while every changed key is hot, since a
|
|
45
|
+
* single cold key in the batch forces the restart anyway.
|
|
46
|
+
*/
|
|
47
|
+
export declare function batchImpact(keys: readonly string[], impactOf: (key: string) => ConfigImpact): ConfigImpact;
|
|
48
|
+
export interface SettingsImpactSchema {
|
|
49
|
+
/** field path → impact, e.g. `instance.tool_progress` → "now". */
|
|
50
|
+
impacts: Record<string, ConfigImpact>;
|
|
51
|
+
/** Escalation order, so the page can cost a batch without knowing the rule. */
|
|
52
|
+
order: readonly ConfigImpact[];
|
|
53
|
+
}
|
|
54
|
+
/** The payload behind `GET /api/settings/schema`. */
|
|
55
|
+
export declare function buildSettingsImpactSchema(): SettingsImpactSchema;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One source of truth for "what does changing this setting cost?".
|
|
3
|
+
*
|
|
4
|
+
* `HOT_INSTANCE_CONFIG_KEYS` is the authority: a hot key is pushed into a live
|
|
5
|
+
* daemon over IPC (`Daemon.applyConfigUpdate`), anything else needs the CLI
|
|
6
|
+
* process restarted. That knowledge used to be spelled out in five places —
|
|
7
|
+
* the fleet manager, the settings API, twice in the settings page, and once per
|
|
8
|
+
* field as a hand-written badge — so the four copies could drift away from the
|
|
9
|
+
* one that decides what actually happens.
|
|
10
|
+
*
|
|
11
|
+
* Everything here is derived from the two sets below. The settings page reads
|
|
12
|
+
* it from `GET /api/settings/schema` instead of carrying its own copy.
|
|
13
|
+
*/
|
|
14
|
+
import { isDeepStrictEqual } from "node:util";
|
|
15
|
+
/** Cheapest first. A batch of edits costs as much as its worst member. */
|
|
16
|
+
export const IMPACT_ORDER = ["now", "instance", "fleet"];
|
|
17
|
+
/**
|
|
18
|
+
* Instance settings a live daemon can take without a restart.
|
|
19
|
+
*
|
|
20
|
+
* Adding a key here is only half the change: `Daemon.applyConfigUpdate` has to
|
|
21
|
+
* learn it too, or the setting is written to disk, reported as applied, and
|
|
22
|
+
* silently ignored by the running agent. `tests/hot-cold-parity.test.ts` fails
|
|
23
|
+
* when the two drift.
|
|
24
|
+
*/
|
|
25
|
+
export const HOT_INSTANCE_CONFIG_KEYS = new Set([
|
|
26
|
+
"tool_progress",
|
|
27
|
+
"reply_completion_guard",
|
|
28
|
+
"mcp_proxy_reply",
|
|
29
|
+
"auto_pause_after",
|
|
30
|
+
"warm_cap",
|
|
31
|
+
"display_name",
|
|
32
|
+
"description",
|
|
33
|
+
"tags",
|
|
34
|
+
"log_level",
|
|
35
|
+
]);
|
|
36
|
+
/**
|
|
37
|
+
* The hot keys a ClassicBot channel edit can use.
|
|
38
|
+
*
|
|
39
|
+
* Narrower than the fleet-wide set because a classic channel's hot path carries
|
|
40
|
+
* exactly what `FleetManager.classicBehaviorUpdate()` builds; a key that is hot
|
|
41
|
+
* for a fleet instance but absent from that payload (auto_pause_after) would be
|
|
42
|
+
* dropped on the way. Derived, not re-listed: dropping a key from the hot set
|
|
43
|
+
* drops it here too.
|
|
44
|
+
*/
|
|
45
|
+
export const CLASSIC_HOT_CONFIG_KEYS = new Set(["tool_progress", "reply_completion_guard"]
|
|
46
|
+
.filter(key => HOT_INSTANCE_CONFIG_KEYS.has(key)));
|
|
47
|
+
/**
|
|
48
|
+
* Instance keys that are not the instance's own business: they change how the
|
|
49
|
+
* fleet binds and routes the instance, so the fleet process has to restart.
|
|
50
|
+
*/
|
|
51
|
+
const FLEET_SCOPED_INSTANCE_KEYS = new Set([
|
|
52
|
+
"topic_id",
|
|
53
|
+
"channel_id",
|
|
54
|
+
"general_topic",
|
|
55
|
+
]);
|
|
56
|
+
/** Instance-scoped fields the settings page renders. */
|
|
57
|
+
const INSTANCE_FIELDS = [
|
|
58
|
+
"display_name", "description", "backend", "model", "working_directory",
|
|
59
|
+
"auto_pause_after", "warm_cap", "topic_id", "channel_id", "general_topic",
|
|
60
|
+
"tool_progress", "reply_completion_guard", "mcp_proxy_reply", "log_level",
|
|
61
|
+
"systemPrompt", "tags", "hang_detector", "agent_mode", "tool_set",
|
|
62
|
+
"lightweight", "model_failover", "effort", "pre_task_command",
|
|
63
|
+
];
|
|
64
|
+
/** ClassicBot channel fields the settings page renders. */
|
|
65
|
+
const CLASSIC_FIELDS = [
|
|
66
|
+
"backend", "model", "auto_pause_after", "tool_progress",
|
|
67
|
+
"reply_completion_guard", "collab", "context_lines", "pre_task_command",
|
|
68
|
+
];
|
|
69
|
+
/**
|
|
70
|
+
* Fleet-level fields, which no per-key rule can derive: they belong to the
|
|
71
|
+
* fleet process or the channel binding rather than to an instance's config.
|
|
72
|
+
*/
|
|
73
|
+
const FLEET_FIELD_IMPACTS = {
|
|
74
|
+
"defaults.locale": "now",
|
|
75
|
+
"fleet.channels": "fleet",
|
|
76
|
+
"fleet.channel.access.mode": "fleet",
|
|
77
|
+
"fleet.channel.access.allowed_users": "fleet",
|
|
78
|
+
"fleet.spawn_concurrency": "fleet",
|
|
79
|
+
"fleet.spawn_stagger_ms": "fleet",
|
|
80
|
+
"classic.admin_users": "fleet",
|
|
81
|
+
"classic.allowed_guilds": "fleet",
|
|
82
|
+
"instance.delete": "fleet",
|
|
83
|
+
};
|
|
84
|
+
/** Split a config into what a live daemon can take and what needs a restart. */
|
|
85
|
+
export function splitHotColdConfig(config) {
|
|
86
|
+
const hot = {};
|
|
87
|
+
const cold = {};
|
|
88
|
+
for (const [key, value] of Object.entries(config)) {
|
|
89
|
+
(HOT_INSTANCE_CONFIG_KEYS.has(key) ? hot : cold)[key] = value;
|
|
90
|
+
}
|
|
91
|
+
return { hot, cold };
|
|
92
|
+
}
|
|
93
|
+
/** The complete hot snapshot sent over IPC; absent values are explicit nulls so
|
|
94
|
+
* the daemon removes them rather than keeping a stale override. */
|
|
95
|
+
export function hotConfigUpdate(config) {
|
|
96
|
+
const update = {};
|
|
97
|
+
for (const key of HOT_INSTANCE_CONFIG_KEYS)
|
|
98
|
+
update[key] = config[key] ?? null;
|
|
99
|
+
return update;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* What applying `next` to a running instance costs.
|
|
103
|
+
*
|
|
104
|
+
* The one comparison `reconcileInstances` makes when it decides between an IPC
|
|
105
|
+
* hot update and a stop/start — shared so the apply job's plan says what the
|
|
106
|
+
* reconcile will actually do rather than holding a second opinion about it.
|
|
107
|
+
*/
|
|
108
|
+
export function classifyInstanceChange(runtime, next) {
|
|
109
|
+
const before = splitHotColdConfig(runtime);
|
|
110
|
+
const after = splitHotColdConfig(next);
|
|
111
|
+
// Every field not explicitly classified hot is cold by default.
|
|
112
|
+
if (!isDeepStrictEqual(before.cold, after.cold))
|
|
113
|
+
return "restart";
|
|
114
|
+
if (!isDeepStrictEqual(before.hot, after.hot))
|
|
115
|
+
return "hot";
|
|
116
|
+
return "none";
|
|
117
|
+
}
|
|
118
|
+
export function instanceFieldImpact(key) {
|
|
119
|
+
if (FLEET_SCOPED_INSTANCE_KEYS.has(key))
|
|
120
|
+
return "fleet";
|
|
121
|
+
return HOT_INSTANCE_CONFIG_KEYS.has(key) ? "now" : "instance";
|
|
122
|
+
}
|
|
123
|
+
export function classicFieldImpact(key) {
|
|
124
|
+
return CLASSIC_HOT_CONFIG_KEYS.has(key) ? "now" : "instance";
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Impact of one staged edit: hot only while every changed key is hot, since a
|
|
128
|
+
* single cold key in the batch forces the restart anyway.
|
|
129
|
+
*/
|
|
130
|
+
export function batchImpact(keys, impactOf) {
|
|
131
|
+
let worst = 0;
|
|
132
|
+
for (const key of keys)
|
|
133
|
+
worst = Math.max(worst, IMPACT_ORDER.indexOf(impactOf(key)));
|
|
134
|
+
return IMPACT_ORDER[worst];
|
|
135
|
+
}
|
|
136
|
+
/** The payload behind `GET /api/settings/schema`. */
|
|
137
|
+
export function buildSettingsImpactSchema() {
|
|
138
|
+
const impacts = { ...FLEET_FIELD_IMPACTS };
|
|
139
|
+
for (const key of INSTANCE_FIELDS) {
|
|
140
|
+
impacts[`instance.${key}`] = instanceFieldImpact(key);
|
|
141
|
+
// Fleet defaults carry the same restart cost as the instance field they
|
|
142
|
+
// seed — except the fleet-scoped ones, which are not defaults at all.
|
|
143
|
+
if (!FLEET_SCOPED_INSTANCE_KEYS.has(key))
|
|
144
|
+
impacts[`defaults.${key}`] = instanceFieldImpact(key);
|
|
145
|
+
}
|
|
146
|
+
for (const key of CLASSIC_FIELDS) {
|
|
147
|
+
impacts[`classic.${key}`] = classicFieldImpact(key);
|
|
148
|
+
impacts[`classic_defaults.${key}`] = classicFieldImpact(key);
|
|
149
|
+
}
|
|
150
|
+
return { impacts, order: IMPACT_ORDER };
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=instance-config-impact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instance-config-impact.js","sourceRoot":"","sources":["../src/instance-config-impact.ts"],"names":[],"mappings":"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"}
|
|
@@ -27,6 +27,7 @@ export const BACKEND_INSTALLATION_INFO = {
|
|
|
27
27
|
opencode: { binary: "opencode", install: "curl -fsSL https://opencode.ai/install | bash" },
|
|
28
28
|
antigravity: { binary: "agy", install: "curl -fsSL https://antigravity.google/cli/install.sh | bash" },
|
|
29
29
|
grok: { binary: "grok", install: "curl -fsSL https://x.ai/cli/install.sh | bash" },
|
|
30
|
+
muse: { binary: "muse", install: "curl -fsSL https://api.meta.ai/muse-launcher.sh | bash" },
|
|
30
31
|
};
|
|
31
32
|
/** Check one executable using the same PATH visible to the fleet process. */
|
|
32
33
|
export function checkBinaryInstalled(binary) {
|
|
@@ -674,10 +675,13 @@ export class InstanceLifecycle {
|
|
|
674
675
|
this.ctx.logger.warn({ name, backend: "codex" }, "Codex quota verification failed or timed out — pausing conservatively");
|
|
675
676
|
}
|
|
676
677
|
}
|
|
677
|
-
// Claude's credit-balance
|
|
678
|
-
// a fresh usage API row can prove
|
|
679
|
-
// statusline, timeout, or missing credentials remains
|
|
680
|
-
|
|
678
|
+
// Claude's credit-balance and hard usage-pause messages can remain in pane
|
|
679
|
+
// scrollback. Only a fresh usage API row can prove them stale; provider
|
|
680
|
+
// fallback to a local statusline, timeout, or missing credentials remains
|
|
681
|
+
// fail-closed. `verifyQuota` lets a notify-only pattern request the same
|
|
682
|
+
// second opinion without entering the destructive pause action below.
|
|
683
|
+
if (data.type === "quota" && this.backendOf(name) === "claude-code"
|
|
684
|
+
&& (data.action === "pause" || data.verifyQuota)) {
|
|
681
685
|
const verdict = await this.verifyClaudeQuota();
|
|
682
686
|
if (verdict === "available") {
|
|
683
687
|
this.ctx.logger.debug({ name, backend: "claude-code" }, "quota pattern ignored — live usage has capacity (stale pane history)");
|