@songsid/agend 2.3.0-beta.22 → 2.3.0-beta.23
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/README.md +2 -2
- package/README.zh-TW.md +2 -2
- package/dist/agent-cli.js +4 -2
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.d.ts +1 -0
- package/dist/agent-endpoint.js +4 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-whats-new.d.ts +5 -0
- package/dist/agent-whats-new.js +16 -1
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/auth-api.d.ts +7 -0
- package/dist/auth-api.js +8 -2
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/codex-package.d.ts +30 -0
- package/dist/backend/codex-package.js +133 -0
- package/dist/backend/codex-package.js.map +1 -0
- package/dist/backend/codex.d.ts +2 -0
- package/dist/backend/codex.js +9 -2
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/kiro.d.ts +9 -1
- package/dist/backend/kiro.js +102 -1
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/opencode-permission.js +1 -1
- package/dist/backend/opencode-permission.js.map +1 -1
- package/dist/backend/opencode.js +2 -1
- package/dist/backend/opencode.js.map +1 -1
- package/dist/binary-probe.js +1 -1
- package/dist/bug-report-cli.js +1 -1
- package/dist/bundled-skills.d.ts +7 -3
- package/dist/bundled-skills.js +16 -7
- package/dist/bundled-skills.js.map +1 -1
- package/dist/channel/adapters/discord.js +3 -3
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/mcp-server.js +12 -4
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/channel/mcp-tools.d.ts +3 -1
- package/dist/channel/mcp-tools.js +36 -0
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/child-process.d.ts +21 -0
- package/dist/child-process.js +54 -0
- package/dist/child-process.js.map +1 -0
- package/dist/cli.js +167 -182
- package/dist/cli.js.map +1 -1
- package/dist/command-table.d.ts +1 -1
- package/dist/command-table.js +1 -1
- package/dist/command-table.js.map +1 -1
- package/dist/config-validator.js +20 -1
- package/dist/config-validator.js.map +1 -1
- package/dist/config.js +3 -1
- package/dist/config.js.map +1 -1
- package/dist/daemon.d.ts +8 -0
- package/dist/daemon.js +31 -8
- package/dist/daemon.js.map +1 -1
- package/dist/darwin-memory.js +1 -1
- package/dist/doctor.d.ts +5 -0
- package/dist/doctor.js +42 -0
- package/dist/doctor.js.map +1 -1
- package/dist/export-import.js +1 -1
- package/dist/export-import.js.map +1 -1
- package/dist/fleet-level-config.d.ts +1 -1
- package/dist/fleet-level-config.js +2 -0
- package/dist/fleet-level-config.js.map +1 -1
- package/dist/fleet-manager.d.ts +45 -0
- package/dist/fleet-manager.js +301 -21
- package/dist/fleet-manager.js.map +1 -1
- package/dist/full-restart.d.ts +5 -5
- package/dist/full-restart.js +5 -5
- package/dist/full-restart.js.map +1 -1
- package/dist/general-knowledge/skills/fleet-config/SKILL.md +1 -1
- package/dist/general-knowledge/skills/fleet-restart/SKILL.md +7 -8
- package/dist/general-knowledge/skills/multi-channel/SKILL.md +1 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +319 -0
- package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +529 -0
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +16 -3
- package/dist/general-knowledge/steering/core-rules.md +1 -1
- package/dist/instance-config-impact.js +10 -3
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.js +4 -4
- package/dist/locale.js +118 -4
- package/dist/locale.js.map +1 -1
- package/dist/login-flows.js +1 -1
- package/dist/login-shell.js +3 -1
- package/dist/login-shell.js.map +1 -1
- package/dist/ls-rows.js +1 -1
- package/dist/needs-you-hub.d.ts +98 -1
- package/dist/needs-you-hub.js +313 -44
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you-live.d.ts +5 -2
- package/dist/needs-you-live.js +6 -3
- package/dist/needs-you-live.js.map +1 -1
- package/dist/needs-you.d.ts +58 -3
- package/dist/needs-you.js +36 -3
- package/dist/needs-you.js.map +1 -1
- package/dist/outbound-schemas.d.ts +3 -3
- package/dist/public-link-progress.d.ts +12 -1
- package/dist/public-link-progress.js +5 -10
- package/dist/public-link-progress.js.map +1 -1
- package/dist/public-tunnel-config.d.ts +5 -0
- package/dist/public-tunnel-config.js +45 -1
- package/dist/public-tunnel-config.js.map +1 -1
- package/dist/public-web-link.js +6 -5
- package/dist/public-web-link.js.map +1 -1
- package/dist/quickstart.js +14 -8
- package/dist/quickstart.js.map +1 -1
- package/dist/resource-report.js +1 -1
- package/dist/scheduler/task-board.d.ts +41 -1
- package/dist/scheduler/task-board.js +150 -4
- package/dist/scheduler/task-board.js.map +1 -1
- package/dist/sd-notify.js +2 -1
- package/dist/sd-notify.js.map +1 -1
- package/dist/service-installer.d.ts +69 -0
- package/dist/service-installer.js +181 -0
- package/dist/service-installer.js.map +1 -1
- package/dist/service-owner.d.ts +44 -0
- package/dist/service-owner.js +48 -0
- package/dist/service-owner.js.map +1 -0
- package/dist/settings-api.d.ts +4 -0
- package/dist/settings-api.js +21 -3
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +85 -2
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-effect.js +2 -0
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.d.ts +2 -0
- package/dist/settings-http-confirmation.js +3 -2
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/settings-request-capability.d.ts +4 -1
- package/dist/settings-request-capability.js +11 -3
- package/dist/settings-request-capability.js.map +1 -1
- package/dist/setup-host.js +1 -1
- package/dist/setup-wizard.js +1 -1
- package/dist/setup-wizard.js.map +1 -1
- package/dist/task-board-summary.d.ts +101 -0
- package/dist/task-board-summary.js +177 -0
- package/dist/task-board-summary.js.map +1 -0
- package/dist/task-board-waker.d.ts +8 -1
- package/dist/task-board-waker.js +15 -6
- package/dist/task-board-waker.js.map +1 -1
- package/dist/tmux-control.js +1 -1
- package/dist/tmux-control.js.map +1 -1
- package/dist/tmux-exec-helper.d.ts +5 -1
- package/dist/tmux-exec-helper.js +13 -21
- package/dist/tmux-exec-helper.js.map +1 -1
- package/dist/tmux-manager.js +1 -1
- package/dist/tmux-read.js +1 -1
- package/dist/tmux-read.js.map +1 -1
- package/dist/topic-commands.d.ts +3 -0
- package/dist/topic-commands.js +29 -18
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-monitor.js +5 -0
- package/dist/transcript-monitor.js.map +1 -1
- package/dist/transcript-sources.d.ts +2 -0
- package/dist/transcript-sources.js +4 -0
- package/dist/transcript-sources.js.map +1 -1
- package/dist/tunnel/cloudflared-install.js +3 -1
- package/dist/tunnel/cloudflared-install.js.map +1 -1
- package/dist/tunnel/cloudflared.d.ts +1 -1
- package/dist/tunnel/cloudflared.js +51 -10
- package/dist/tunnel/cloudflared.js.map +1 -1
- package/dist/tunnel/named-diagnostics.d.ts +13 -0
- package/dist/tunnel/named-diagnostics.js +52 -0
- package/dist/tunnel/named-diagnostics.js.map +1 -0
- package/dist/tunnel/types.d.ts +2 -1
- package/dist/tunnel/types.js.map +1 -1
- package/dist/types.d.ts +9 -0
- package/dist/ui/app-effects.js +10 -4
- package/dist/ui/chat-strings.js +3 -0
- package/dist/ui/chat-thread.js +14 -7
- package/dist/ui/effect-trial-offer.js +2 -31
- package/dist/ui/effects-waker.js +2 -70
- package/dist/ui/fleet-task-card.js +5 -5
- package/dist/ui/fleet-task-common.js +41 -7
- package/dist/ui/fleet-task-work.js +107 -0
- package/dist/ui/fleet-tasks.js +46 -12
- package/dist/ui/image-lightbox.js +39 -1
- package/dist/ui/image-zoom.js +242 -0
- package/dist/ui/panel-chat.js +24 -13
- package/dist/ui/panel-details.js +2 -0
- package/dist/ui/panel-fleet.js +6 -2
- package/dist/ui/panel-needs.js +21 -6
- package/dist/ui/panel-settings.js +35 -13
- package/dist/ui/preview-panel.js +2 -1
- package/dist/ui/search-dialog.js +2 -1
- package/dist/ui/settings-appearance.js +33 -8
- package/dist/ui/settings-dialogs.js +33 -17
- package/dist/ui/settings-discord.js +6 -2
- package/dist/ui/settings-guests.js +2 -1
- package/dist/ui/settings-model.js +2 -11
- package/dist/ui/settings-mods.js +6 -3
- package/dist/ui/settings-strings.js +2 -0
- package/dist/ui/settings-token.js +3 -1
- package/dist/ui/settings-web-authority.js +6 -3
- package/dist/ui/settings-wizard.js +13 -7
- package/dist/ui/shared/app-i18n.js +2 -2
- package/dist/ui/shared/app-needs.js +2 -2
- package/dist/ui/shared/app-store.js +2 -2
- package/dist/ui/shared/app.css +80 -9
- package/dist/ui/shared/app.js +5 -1
- package/dist/ui/shared/effect-app-runtime.js +7 -5
- package/dist/ui/shared/effect-chat-runtime.js +2 -7
- package/dist/ui/shared/effect-cursor.js +3 -2
- package/dist/ui/shared/effect-display-metadata.js +39 -0
- package/dist/ui/shared/effect-registry.js +4 -2
- package/dist/ui/shared/effect-runtime-loader.js +3 -1
- package/dist/ui/shared/effect-stock-trust.js +1 -1
- package/dist/ui/shared/feature-flags.js +127 -0
- package/dist/ui/shared/identity-color.js +6 -3
- package/dist/ui/shared/instance-nav.js +3 -2
- package/dist/ui/shared/text-undo.js +210 -0
- package/dist/ui/shared/ui-dialog.js +4 -3
- package/dist/ui/shared/ui-icons.js +2 -0
- package/dist/update-dispatch.js +1 -1
- package/dist/update-launch.d.ts +1 -1
- package/dist/update-launch.js +1 -2
- package/dist/update-launch.js.map +1 -1
- package/dist/usage/providers.js +1 -1
- package/dist/usage/providers.js.map +1 -1
- package/dist/view-api.js +1 -1
- package/dist/web-api.d.ts +6 -0
- package/dist/web-api.js +152 -14
- package/dist/web-api.js.map +1 -1
- package/dist/web-appearance-tools.d.ts +1 -0
- package/dist/web-appearance-tools.js +7 -3
- package/dist/web-appearance-tools.js.map +1 -1
- package/dist/web-appearance.d.ts +4 -10
- package/dist/web-appearance.js +4 -3
- package/dist/web-appearance.js.map +1 -1
- package/dist/web-chat-history.d.ts +14 -0
- package/dist/web-chat-history.js +26 -1
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-experimental-http.d.ts +2 -0
- package/dist/web-experimental-http.js +12 -0
- package/dist/web-experimental-http.js.map +1 -0
- package/dist/web-experimental.d.ts +11 -0
- package/dist/web-experimental.js +49 -0
- package/dist/web-experimental.js.map +1 -0
- package/dist/web-guest-access.d.ts +1 -0
- package/dist/web-guest-access.js +4 -2
- package/dist/web-guest-access.js.map +1 -1
- package/dist/web-mod-store.d.ts +2 -0
- package/dist/web-mod-store.js +10 -0
- package/dist/web-mod-store.js.map +1 -1
- package/dist/web-preview.d.ts +2 -0
- package/dist/web-preview.js +2 -0
- package/dist/web-preview.js.map +1 -1
- package/dist/web-settings-patch.d.ts +2 -1
- package/dist/web-settings-patch.js +5 -1
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-settings-tools.d.ts +1 -0
- package/dist/web-settings-tools.js +14 -10
- package/dist/web-settings-tools.js.map +1 -1
- package/dist/web-terminal.js +1 -1
- package/dist/web-theme-store.d.ts +2 -2
- package/dist/web-theme-store.js +6 -3
- package/dist/web-theme-store.js.map +1 -1
- package/package.json +1 -1
package/dist/fleet-manager.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PUBLIC_TUNNEL_TOKEN_ENV,
|
|
1
|
+
import { PUBLIC_TUNNEL_TOKEN_ENV, PUBLIC_TUNNEL_TOKEN_HINT, normalizePublicTunnelToken } from "./public-tunnel-config.js";
|
|
2
2
|
import { humanOwnerObjectId, webHumanOrigin } from "./human-delivery-receipts.js";
|
|
3
3
|
import { SettingsBaselines } from "./settings-baseline.js";
|
|
4
4
|
import { SettingsConfirmationStore } from "./settings-confirmation.js";
|
|
@@ -77,10 +77,12 @@ import { routeToolCall } from "./channel/tool-router.js";
|
|
|
77
77
|
import { Scheduler } from "./scheduler/index.js";
|
|
78
78
|
import { boardActivity, boardCardLimit, boardToolAction } from "./task-board-tool.js";
|
|
79
79
|
import { TaskBoardWaker } from "./task-board-waker.js";
|
|
80
|
+
import { TaskBoardError, wrapUpGap } from "./scheduler/task-board.js";
|
|
80
81
|
import { escapeTelegramHtml, scheduleClock, scheduleRetryLabel } from "./scheduler/retry-label.js";
|
|
81
82
|
import { DEFAULT_SCHEDULER_CONFIG } from "./scheduler/index.js";
|
|
82
83
|
import { TopicCommands, saveCommandForBackend, parseSaveFilename, parsePauseWakeCommand, parseCompactCommand, SAVE_FILENAME_RE, resolveInstanceContext, forgetInstanceContext, readStatuslineJson, statuslineModel } from "./topic-commands.js";
|
|
83
84
|
import { DailySummary } from "./daily-summary.js";
|
|
85
|
+
import { TaskBoardSummaryClock, buildSummary, isQuiet, renderSummary, summaryMarkFile, summaryTime } from "./task-board-summary.js";
|
|
84
86
|
import { WebhookEmitter } from "./webhook-emitter.js";
|
|
85
87
|
import { TmuxControlClient } from "./tmux-control.js";
|
|
86
88
|
import { safeHandler } from "./safe-async.js";
|
|
@@ -247,6 +249,9 @@ import { decisionListAnswer } from "./decision-list.js";
|
|
|
247
249
|
import { CodeRequestLimiter, ReturningDevices } from "./web-returning-device.js";
|
|
248
250
|
import { allowedHostNames, applyWebSecurityHeaders, hostnameOf, isHostAllowed, LOOPBACK_HOST_NAMES, WEB_HOST_REJECTED_MESSAGE } from "./web-host-guard.js";
|
|
249
251
|
import { createPreviewListener, normalizeOrigin, previewAvailability, previewSettings } from "./web-preview.js";
|
|
252
|
+
import { canvasWorkerEnabled, isCanvasWorkerTool, CANVAS_WORKER_EXPERIMENTAL_MESSAGE } from "./web-experimental.js";
|
|
253
|
+
import { isCanvasWorkerHttpPath } from "./web-experimental-http.js";
|
|
254
|
+
import { bundledSkillContents } from "./bundled-skills.js";
|
|
250
255
|
import { createEffectBootstrap } from "./web-effect-bootstrap.js";
|
|
251
256
|
import { fleetLevelDifferences, fleetLevelSignature, appendedConnections } from "./fleet-level-config.js";
|
|
252
257
|
import { checkSelfRestartAllowance, recordSelfRestartAttempt } from "./self-restart-limit.js";
|
|
@@ -263,6 +268,7 @@ import { mergeBackendOptions, profileKey, profileName, profileOf } from "./class
|
|
|
263
268
|
import { classifyInstanceChange, CLASSIC_HOT_CONFIG_KEYS, HOT_INSTANCE_CONFIG_KEYS, hotConfigUpdate, } from "./instance-config-impact.js";
|
|
264
269
|
import { cleanupWatchVerdict, formatRestartProgressCompletion, RestartProgress, } from "./restart-progress.js";
|
|
265
270
|
import { launchFullRestartHelper } from "./full-restart.js";
|
|
271
|
+
import { fullReloadExitCode } from "./service-owner.js";
|
|
266
272
|
import { SYSTEMD_RESTART_INDETERMINATE_EXIT_CODE } from "./service-restart-selection.js";
|
|
267
273
|
import { collectRedundantInstanceDefaultPaths } from "./fleet-yaml-slim.js";
|
|
268
274
|
import { StormWindow } from "./storm-window.js";
|
|
@@ -673,6 +679,8 @@ export class FleetManager {
|
|
|
673
679
|
costGuard = null;
|
|
674
680
|
statuslineWatcher;
|
|
675
681
|
dailySummary = null;
|
|
682
|
+
/** #2246 step 3: the task board's daily summary for the person (task-board-summary.ts). */
|
|
683
|
+
taskBoardSummary = null;
|
|
676
684
|
dailyTipScheduler = null;
|
|
677
685
|
/** The task board's wakes (step 2): an instance hears when someone else changes a card it owns or handed out. */
|
|
678
686
|
taskBoardWaker = null;
|
|
@@ -849,6 +857,7 @@ export class FleetManager {
|
|
|
849
857
|
effectBootstrap = createEffectBootstrap();
|
|
850
858
|
/** Fixed trusted document coherence, independent of the HTML preview listener; never a resource grant. */
|
|
851
859
|
get effectBoot() { return this.effectBootstrap.boot; }
|
|
860
|
+
canvasWorkerEnabled() { return canvasWorkerEnabled(this.fleetConfig?.web); }
|
|
852
861
|
healthServerDrain = null;
|
|
853
862
|
/** #1306: the preview listener (health_port + 1 by default), and whether it is listening. */
|
|
854
863
|
previewListener = null;
|
|
@@ -1095,6 +1104,7 @@ export class FleetManager {
|
|
|
1095
1104
|
/** #1629 r4 §1: get_web_appearance / preview_web_appearance — the caller's own look, resolved as the page resolves it. */
|
|
1096
1105
|
get appearanceTools() {
|
|
1097
1106
|
return (this.appearanceToolsService ??= new AppearanceTools({
|
|
1107
|
+
canvasWorkerEnabled: () => this.canvasWorkerEnabled(),
|
|
1098
1108
|
frame: (name) => (this.getUiStatus().instances.find(i => i.name === name) ?? { name }),
|
|
1099
1109
|
theme: (name) => {
|
|
1100
1110
|
const r = this.themeStore.get(name);
|
|
@@ -1139,6 +1149,7 @@ export class FleetManager {
|
|
|
1139
1149
|
prefs: this.webPrefs,
|
|
1140
1150
|
ownerCss: this.ownerCss,
|
|
1141
1151
|
effects: this.effectLibrary,
|
|
1152
|
+
canvasWorkerEnabled: () => this.canvasWorkerEnabled(),
|
|
1142
1153
|
context: () => ({ agendVersion: agendVersion(), limits: this.themeStore.limits(), effects: cw1EffectChecker() }),
|
|
1143
1154
|
isKnownInstance: (name) => this.isKnownInstance(name),
|
|
1144
1155
|
prefsIdentity: OPERATOR,
|
|
@@ -1194,6 +1205,7 @@ export class FleetManager {
|
|
|
1194
1205
|
*/
|
|
1195
1206
|
get webSettings() {
|
|
1196
1207
|
return (this.webSettingsToolsService ??= new WebSettingsTools({
|
|
1208
|
+
canvasWorkerEnabled: () => this.canvasWorkerEnabled(),
|
|
1197
1209
|
prefs: this.webPrefs, ownerCss: this.ownerCss, mods: this.mods, effects: this.effectService,
|
|
1198
1210
|
instances: () => [...Object.keys(this.fleetConfig?.instances ?? {}),
|
|
1199
1211
|
...(this.classicChannels?.getAll() ?? []).map((ch) => ch.instanceName).filter((n) => !Object.hasOwn(this.fleetConfig?.instances ?? {}, n))],
|
|
@@ -1311,6 +1323,7 @@ export class FleetManager {
|
|
|
1311
1323
|
return null;
|
|
1312
1324
|
const record = auth.session, sessions = this.webSessions, exposure = gatewayRequestContext(req);
|
|
1313
1325
|
return { id: record.idHash, label: record.label, source: exposure ? "public_link" : "web_session",
|
|
1326
|
+
localAdmin: () => record.tier === "admin" && record.surface === "local" && !exposure,
|
|
1314
1327
|
current: () => current() && sessions === this.webSessions && !!this.webToken
|
|
1315
1328
|
&& sessions.isCurrent(record, tokenEpoch(this.webToken)) && (!exposure || exposure.isCurrent()) };
|
|
1316
1329
|
}, baseline: () => baselines.read(), snapshot: () => baselines.snapshot(),
|
|
@@ -2659,6 +2672,10 @@ export class FleetManager {
|
|
|
2659
2672
|
this.savedFleetConfigSnapshot = structuredClone(this.fleetConfig);
|
|
2660
2673
|
this.warnAboutRemovedLoginMode(loaded);
|
|
2661
2674
|
this.enforceGuestSwitch();
|
|
2675
|
+
// Child tool/guidance readers must follow this fleet's actual config, including --config.
|
|
2676
|
+
// Scope the path to its owning data directory so another inherited home's override cannot enable CW.
|
|
2677
|
+
process.env.AGEND_FLEET_DATA_DIR = resolvePath(this.dataDir);
|
|
2678
|
+
process.env.AGEND_FLEET_CONFIG_PATH = resolvePath(configPath);
|
|
2662
2679
|
return this.fleetConfig;
|
|
2663
2680
|
}
|
|
2664
2681
|
relayLoginModeWarned = false;
|
|
@@ -2838,8 +2855,17 @@ export class FleetManager {
|
|
|
2838
2855
|
this.logger.info(`Received SIGUSR1, initiating full restart (process reload)... ${describeSignalSource(this.dataDir, "SIGUSR1")}`);
|
|
2839
2856
|
this.gracefulShutdownForReload()
|
|
2840
2857
|
.then(() => {
|
|
2841
|
-
|
|
2842
|
-
|
|
2858
|
+
// #2356: under an AgEnD systemd unit a clean exit is a stop (Restart=on-failure); exit so the unit comes back.
|
|
2859
|
+
let ownCgroup = null;
|
|
2860
|
+
try {
|
|
2861
|
+
ownCgroup = readFileSync("/proc/self/cgroup", "utf-8");
|
|
2862
|
+
}
|
|
2863
|
+
catch { /* not Linux */ }
|
|
2864
|
+
const code = fullReloadExitCode(ownCgroup);
|
|
2865
|
+
this.logger.info(code === 0
|
|
2866
|
+
? "Full restart: shutdown complete, exiting for reload"
|
|
2867
|
+
: `Full restart: shutdown complete, exiting ${code} so the systemd unit restarts the fleet (#2356)`);
|
|
2868
|
+
process.exit(code);
|
|
2843
2869
|
})
|
|
2844
2870
|
.catch(err => {
|
|
2845
2871
|
this.logger.error({ err }, "Full restart: graceful shutdown failed");
|
|
@@ -2858,7 +2884,7 @@ export class FleetManager {
|
|
|
2858
2884
|
const chatId = String(this.getChannelConfig(adapterId)?.group_id ?? data.channelId);
|
|
2859
2885
|
this.beginUpdateProgress(adapter, chatId, data.channelId, messageId);
|
|
2860
2886
|
}
|
|
2861
|
-
const { spawn } = await import("
|
|
2887
|
+
const { spawn } = await import("./child-process.js");
|
|
2862
2888
|
// Plain `agend update`: the CLI picks the channel from the version it is about to replace (a beta install
|
|
2863
2889
|
// stays on beta). Deciding here, from the package.json next to THIS code, read a source checkout's 1.22.0 as
|
|
2864
2890
|
// "not a beta" and sent a beta install to @latest. #1450 C5: the INSTALLED agend, verified, by absolute path.
|
|
@@ -4304,6 +4330,18 @@ export class FleetManager {
|
|
|
4304
4330
|
async dispatchSlashCore(slash, adapterId, adapter) {
|
|
4305
4331
|
// #1569: an alias (/web) is its command (/dashboard) from here on — the door, the table and the handler see one name.
|
|
4306
4332
|
const data = { ...slash, command: canonicalCommand(slash.command) };
|
|
4333
|
+
// #2359: /dashboard stays listed; prefix its reply with the deprecation notice, then run /web's handler.
|
|
4334
|
+
const noteDeprecatedDashboard = () => {
|
|
4335
|
+
if (slash.command !== "dashboard")
|
|
4336
|
+
return;
|
|
4337
|
+
const prefix = `${t("dashboard.deprecated")}\n\n`;
|
|
4338
|
+
const respond = data.respond;
|
|
4339
|
+
data.respond = async (text) => respond(prefix + text);
|
|
4340
|
+
if (data.respondButtons) {
|
|
4341
|
+
const respondButtons = data.respondButtons;
|
|
4342
|
+
data.respondButtons = async (text, choices) => respondButtons(prefix + text, choices);
|
|
4343
|
+
}
|
|
4344
|
+
};
|
|
4307
4345
|
const door = this.slashDoor(data, adapterId);
|
|
4308
4346
|
if ("refusal" in door) {
|
|
4309
4347
|
await data.respond(t(door.refusal)).catch(() => { });
|
|
@@ -4314,6 +4352,7 @@ export class FleetManager {
|
|
|
4314
4352
|
// — before the table, which keeps /web for fleet admins. Per account: it touches no instance binding.
|
|
4315
4353
|
// Decided synchronously, so an admin's /web still reaches the table with no await after the door (#1399).
|
|
4316
4354
|
if (data.command === "dashboard" && this.isGuestWebAsk(adapterId, data.userId)) {
|
|
4355
|
+
noteDeprecatedDashboard();
|
|
4317
4356
|
await this.guestWebCommand({ adapterId, userId: data.userId, username: data.username,
|
|
4318
4357
|
arg: String(data.options?.action ?? "").trim().toLowerCase(), replyHere: text => data.respond(text), replyPrivately: text => data.respond(text) });
|
|
4319
4358
|
return;
|
|
@@ -4342,6 +4381,7 @@ export class FleetManager {
|
|
|
4342
4381
|
await data.respond(reply).catch(() => { });
|
|
4343
4382
|
return;
|
|
4344
4383
|
}
|
|
4384
|
+
noteDeprecatedDashboard();
|
|
4345
4385
|
if (data.command === "start") {
|
|
4346
4386
|
await this.handleClassicStartSlash(data, adapterId);
|
|
4347
4387
|
}
|
|
@@ -7158,6 +7198,17 @@ export class FleetManager {
|
|
|
7158
7198
|
return DailySummary.generateText(this.eventLog, instances, costMap, this.costGuard?.getFleetTotalCents() ?? 0);
|
|
7159
7199
|
});
|
|
7160
7200
|
this.dailySummary.start();
|
|
7201
|
+
// #2246 step 3: the task board's daily summary — its own time (defaults.task_board.summary_at, read at every look),
|
|
7202
|
+
// in the fleet's zone; a summary missed while the fleet was down goes out when it is back.
|
|
7203
|
+
this.taskBoardSummary = new TaskBoardSummaryClock({
|
|
7204
|
+
at: () => summaryTime(this.fleetConfig?.defaults?.task_board?.summary_at) ?? null,
|
|
7205
|
+
zone: () => this.usageZone(),
|
|
7206
|
+
now: () => new Date(),
|
|
7207
|
+
mark: summaryMarkFile(join(this.dataDir, "task-board-summary.json")),
|
|
7208
|
+
send: (since, until) => this.postTaskBoardSummary(since, until),
|
|
7209
|
+
failed: (err) => this.logger.warn({ err }, "The task board's daily summary failed; the next minute tries again"),
|
|
7210
|
+
});
|
|
7211
|
+
this.taskBoardSummary.start();
|
|
7161
7212
|
// Rotate classic channel chat logs daily (piggyback on daily summary timer)
|
|
7162
7213
|
this.classicChannels?.rotateLogs();
|
|
7163
7214
|
this.rotateInboxes();
|
|
@@ -7552,7 +7603,8 @@ export class FleetManager {
|
|
|
7552
7603
|
}, 60 * 60 * 1000);
|
|
7553
7604
|
const onRestart = () => this.onGracefulRestartSignal(() => process.once("SIGUSR2", onRestart));
|
|
7554
7605
|
process.once("SIGUSR2", onRestart);
|
|
7555
|
-
// SIGUSR1: full process reload (graceful stop → exit
|
|
7606
|
+
// SIGUSR1: full process reload (graceful stop → exit). Nothing in this AgEnD sends it any more (#2356); an older
|
|
7607
|
+
// CLI or a hand-sent signal still may, and the exit code then lets the owning manager start the next fleet.
|
|
7556
7608
|
process.once("SIGUSR1", () => this.onFullRestartSignal());
|
|
7557
7609
|
// A SIGHUP may arrive after the PID/general is available but before the
|
|
7558
7610
|
// rest of startup finishes. Replay one coalesced reload only after all
|
|
@@ -8443,7 +8495,7 @@ export class FleetManager {
|
|
|
8443
8495
|
// means we cannot verify the pane, which is treated as dead (the same
|
|
8444
8496
|
// conclusion the old code reached only by throwing).
|
|
8445
8497
|
try {
|
|
8446
|
-
const { execFile } = await import("
|
|
8498
|
+
const { execFile } = await import("./child-process.js");
|
|
8447
8499
|
const { promisify } = await import("node:util");
|
|
8448
8500
|
const { getTmuxSocketName } = await import("./paths.js");
|
|
8449
8501
|
// Honour socket isolation: without -L this queried the user's default
|
|
@@ -8993,6 +9045,22 @@ export class FleetManager {
|
|
|
8993
9045
|
this.logger.debug({ adapterId: msg.adapterId, threadId: threadId ?? null, messageId: msg.messageId, reason: drop }, "Inbound message dropped before the dedup claim — key not consumed");
|
|
8994
9046
|
return;
|
|
8995
9047
|
}
|
|
9048
|
+
// #2246 step 3 (#2341 review r2): a reply to a Needs you Send back / Answer prompt is the card's reason or answer, not
|
|
9049
|
+
// input for an instance — judged by the PROMPT's world (its admins, its instance, its room as it is now; the hub's
|
|
9050
|
+
// handleReply), before the room's generic access below: in a shared group the room may be another bot's, whose
|
|
9051
|
+
// policy has no say over that world's prompt. Only that exact reply, by the admin who clicked, while the prompt
|
|
9052
|
+
// stands. Taken once: another bot's copy of a taken reply is dropped here. All else flows on.
|
|
9053
|
+
if (!msg.isBotMessage && msg.replyTo && this.needsYou) {
|
|
9054
|
+
if (this.needsYou.wasTakenReply(msg.chatId, msg.messageId)) {
|
|
9055
|
+
this.logger.debug({ adapterId: msg.adapterId, messageId: msg.messageId }, "Another bot's copy of a Needs you answer already taken — dropped");
|
|
9056
|
+
return;
|
|
9057
|
+
}
|
|
9058
|
+
if (this.needsYou.handleReply({
|
|
9059
|
+
adapterId: msg.adapterId, chatId: msg.chatId, ...(threadId !== undefined ? { threadId } : {}), replyTo: msg.replyTo,
|
|
9060
|
+
messageId: msg.messageId, userId: msg.userId, ...(msg.username ? { username: msg.username } : {}), text: msg.text ?? "",
|
|
9061
|
+
}))
|
|
9062
|
+
return;
|
|
9063
|
+
}
|
|
8996
9064
|
// Access control — classic channels are open to all, others require an allowed
|
|
8997
9065
|
// user. This runs before the dedup claim, and is judged by the adapter that
|
|
8998
9066
|
// owns the topic rather than the one that happened to receive the message:
|
|
@@ -9103,6 +9171,10 @@ export class FleetManager {
|
|
|
9103
9171
|
// #1920 step 4b-3: /web typed in a ClassicBot room (a group: `/web@ThisBot`) by someone who is not a fleet admin,
|
|
9104
9172
|
// while guest sign-in is on: their guest code, privately — as anywhere else. An admin's goes on as before.
|
|
9105
9173
|
if (/^\/(?:web|dashboard)(?:\s|$)/i.test(text) && msgAdapter && this.isGuestWebAsk(msg.adapterId, msg.userId)) {
|
|
9174
|
+
// #2359: this door must not skip the deprecation notice TopicCommands shows on /dashboard.
|
|
9175
|
+
if (/^\/dashboard(?:\s|$)/i.test(text)) {
|
|
9176
|
+
await msgAdapter.sendText(chatId, t("dashboard.deprecated")).catch(() => { });
|
|
9177
|
+
}
|
|
9106
9178
|
await this.guestWebCommand({ adapterId: msg.adapterId, userId: msg.userId, username: msg.username,
|
|
9107
9179
|
arg: text.replace(/^\/(?:web|dashboard)/i, "").trim().toLowerCase(), replyHere: body => msgAdapter.sendText(chatId, body) });
|
|
9108
9180
|
return;
|
|
@@ -9653,6 +9725,8 @@ export class FleetManager {
|
|
|
9653
9725
|
}
|
|
9654
9726
|
}
|
|
9655
9727
|
checkToolPermission(sink, instanceName, tool) {
|
|
9728
|
+
if (isCanvasWorkerTool(tool) && !this.canvasWorkerEnabled())
|
|
9729
|
+
return { allowed: false, message: CANVAS_WORKER_EXPERIMENTAL_MESSAGE };
|
|
9656
9730
|
const profile = resolveToolSet(this.fleetConfig?.instances[instanceName], instanceName);
|
|
9657
9731
|
const allowed = mayUseTool(profile, tool);
|
|
9658
9732
|
if (!allowed) {
|
|
@@ -10337,13 +10411,14 @@ export class FleetManager {
|
|
|
10337
10411
|
if (!ipc || !fleetRequestId)
|
|
10338
10412
|
return;
|
|
10339
10413
|
const type = String(msg.type);
|
|
10340
|
-
const responseType = type.
|
|
10341
|
-
: type.startsWith("
|
|
10342
|
-
: type
|
|
10343
|
-
: type === "
|
|
10344
|
-
: type === "
|
|
10345
|
-
|
|
10346
|
-
|
|
10414
|
+
const responseType = isCanvasWorkerTool(type.replace(/^fleet_/, "")) ? "fleet_web_theme_response"
|
|
10415
|
+
: type.startsWith("fleet_schedule_") ? "fleet_schedule_response"
|
|
10416
|
+
: type.startsWith("fleet_decision_") ? "fleet_decision_response"
|
|
10417
|
+
: type === "fleet_task" ? "fleet_task_response"
|
|
10418
|
+
: type === "fleet_set_display_name" ? "fleet_display_name_response"
|
|
10419
|
+
: type === "fleet_list_emojis" || type === "fleet_set_persona_emoji" || type === "fleet_preview_emojis"
|
|
10420
|
+
|| type === "fleet_list_stickers" || type === "fleet_preview_stickers" ? "fleet_persona_emoji_response"
|
|
10421
|
+
: "fleet_description_response";
|
|
10347
10422
|
ipc.send({ type: responseType, fleetRequestId, error: message });
|
|
10348
10423
|
}
|
|
10349
10424
|
dispatchTypedIpc(name, msg) {
|
|
@@ -10836,6 +10911,8 @@ export class FleetManager {
|
|
|
10836
10911
|
}
|
|
10837
10912
|
/** #1629: `agend-agent theme …` — the same service as the tools; `instance` is the token's. */
|
|
10838
10913
|
async handleWebThemeHttp(instance, tool, args) {
|
|
10914
|
+
if (isCanvasWorkerTool(tool) && !this.canvasWorkerEnabled())
|
|
10915
|
+
return { error: "canvas_worker_experimental", message: CANVAS_WORKER_EXPERIMENTAL_MESSAGE };
|
|
10839
10916
|
if (WEB_EFFECT_TOOLS.has(tool)) {
|
|
10840
10917
|
if (!this.isKnownInstance(instance))
|
|
10841
10918
|
return { error: "unknown_instance" };
|
|
@@ -12728,7 +12805,7 @@ export class FleetManager {
|
|
|
12728
12805
|
const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "");
|
|
12729
12806
|
const backend = this.fleetConfig?.defaults?.backend || "claude-code";
|
|
12730
12807
|
try {
|
|
12731
|
-
const { execFile } = await import("
|
|
12808
|
+
const { execFile } = await import("./child-process.js");
|
|
12732
12809
|
const { promisify } = await import("node:util");
|
|
12733
12810
|
// execFile with an argv array — no shell, so the backend name cannot be
|
|
12734
12811
|
// interpreted as a command even if config is malformed.
|
|
@@ -12954,6 +13031,51 @@ export class FleetManager {
|
|
|
12954
13031
|
}
|
|
12955
13032
|
return { chatId, opts: {} };
|
|
12956
13033
|
}
|
|
13034
|
+
/**
|
|
13035
|
+
* #2246 step 3: the task board's daily summary of [since, until) (task-board-summary.ts). Each chat world's General
|
|
13036
|
+
* gets its own instances' cards — a card that no world's instance holds (nobody's, a person's) goes to the primary
|
|
13037
|
+
* one — and each of that world's Generals shows the same text in the web chat. A world with nothing to say gets
|
|
13038
|
+
* nothing. A web-only fleet: everything, in the web chat.
|
|
13039
|
+
*/
|
|
13040
|
+
postTaskBoardSummary(since, until) {
|
|
13041
|
+
const board = this.scheduler?.db.board;
|
|
13042
|
+
if (!board)
|
|
13043
|
+
return;
|
|
13044
|
+
const read = board.summaryRead(since.toISOString(), until.toISOString());
|
|
13045
|
+
const projects = board.listProjects(true);
|
|
13046
|
+
const stats = board.projectStats();
|
|
13047
|
+
const names = new Map(projects.map((p) => [p.id, p.name]));
|
|
13048
|
+
const configured = this.getPrimaryAdapterId();
|
|
13049
|
+
const primary = configured !== undefined && this.worlds.has(configured) ? configured : this.worlds.keys().next().value;
|
|
13050
|
+
const worldOf = (assignee) => {
|
|
13051
|
+
const owner = assignee ? this.getInstanceAdapterId(assignee) : undefined;
|
|
13052
|
+
return owner !== undefined && this.worlds.has(owner) ? owner : primary;
|
|
13053
|
+
};
|
|
13054
|
+
const zone = this.usageZone();
|
|
13055
|
+
for (const world of this.worlds.size ? [...this.worlds.keys()] : [undefined]) {
|
|
13056
|
+
const summary = buildSummary(read, since, until, { inChat: (assignee) => worldOf(assignee) === world, projects, stats });
|
|
13057
|
+
if (isQuiet(summary))
|
|
13058
|
+
continue;
|
|
13059
|
+
const text = renderSummary(summary, { zone, t: (key, ...args) => t(key, ...args), projectOf: (c) => (c.projectId === null ? null : names.get(c.projectId) ?? null) });
|
|
13060
|
+
const cards = [...new Set([...summary.review, ...summary.blocked, ...summary.done, ...summary.created].map((c) => c.code))];
|
|
13061
|
+
if (world !== undefined) {
|
|
13062
|
+
const adapter = this.worlds.get(world)?.adapter;
|
|
13063
|
+
const target = this.fleetNoticeTarget(world);
|
|
13064
|
+
if (adapter && target) {
|
|
13065
|
+
adapter.sendText(target.chatId, text, target.opts).catch(e => this.logger.warn({ err: e, world }, "Failed to send the task board's daily summary"));
|
|
13066
|
+
}
|
|
13067
|
+
else {
|
|
13068
|
+
this.logger.warn({ world }, "The task board's daily summary has no postable target — set a General topic or channel.options.general_channel_id");
|
|
13069
|
+
}
|
|
13070
|
+
}
|
|
13071
|
+
for (const [name, cfg] of Object.entries(this.fleetConfig?.instances ?? {})) {
|
|
13072
|
+
if (cfg.general_topic !== true || (world !== undefined && this.getInstanceAdapterId(name) !== world))
|
|
13073
|
+
continue;
|
|
13074
|
+
// #2341 review: with the cards it names, so the public link withholds it while one of them is hidden there.
|
|
13075
|
+
this.emitSseEvent("message", { instance: name, sender: name, role: "status", text, ts: new Date().toISOString(), cards });
|
|
13076
|
+
}
|
|
13077
|
+
}
|
|
13078
|
+
}
|
|
12957
13079
|
/**
|
|
12958
13080
|
* Post the daily summary where fleet-wide notices go.
|
|
12959
13081
|
*
|
|
@@ -13662,6 +13784,8 @@ export class FleetManager {
|
|
|
13662
13784
|
return String(topic);
|
|
13663
13785
|
return this.classicChannels?.getChannelIdByInstance(instance) ?? undefined;
|
|
13664
13786
|
},
|
|
13787
|
+
laneTarget: instance => this.instanceChannelTarget(instance),
|
|
13788
|
+
taskRoute: code => this.needsYouRoute(code),
|
|
13665
13789
|
isFleetAdmin: (userId, world) => this.isFleetAdmin(userId, world),
|
|
13666
13790
|
fleetAdmins: world => (this.getChannelConfig(world)?.access?.allowed_users ?? []).map(String),
|
|
13667
13791
|
emitSse: (event, data) => this.emitSseEvent(event, data),
|
|
@@ -13670,6 +13794,8 @@ export class FleetManager {
|
|
|
13670
13794
|
throw new Error("the delivery outbox is not open");
|
|
13671
13795
|
return this.deliveryOutbox.acknowledge(deliveryId, by);
|
|
13672
13796
|
},
|
|
13797
|
+
tasks: () => this.needsYouTasks(),
|
|
13798
|
+
taskAction: action => this.taskActionFromChat(action),
|
|
13673
13799
|
settings: () => ({
|
|
13674
13800
|
liveMessage: this.fleetConfig?.needs_you?.live_message !== false,
|
|
13675
13801
|
dm: this.fleetConfig?.needs_you?.dm === true,
|
|
@@ -13680,6 +13806,98 @@ export class FleetManager {
|
|
|
13680
13806
|
});
|
|
13681
13807
|
this.needsYou.start();
|
|
13682
13808
|
}
|
|
13809
|
+
/**
|
|
13810
|
+
* #2352: where an instance's own notices go — its topic in its world's group, or its ClassicBot room — as a Needs you
|
|
13811
|
+
* lane's place; null when it has neither (or its world has no group).
|
|
13812
|
+
*/
|
|
13813
|
+
instanceChannelTarget(instance) {
|
|
13814
|
+
const cfg = this.fleetConfig?.instances && Object.hasOwn(this.fleetConfig.instances, instance) ? this.fleetConfig.instances[instance] : undefined;
|
|
13815
|
+
if (!cfg)
|
|
13816
|
+
return null;
|
|
13817
|
+
const groupId = this.getChannelConfig(this.getInstanceAdapterId(instance))?.group_id;
|
|
13818
|
+
if (cfg.topic_id != null && groupId != null)
|
|
13819
|
+
return { chatId: String(groupId), threadId: String(cfg.topic_id) };
|
|
13820
|
+
const classic = this.classicChannels?.getChannelIdByInstance(instance);
|
|
13821
|
+
return classic ? { chatId: classic } : null;
|
|
13822
|
+
}
|
|
13823
|
+
/**
|
|
13824
|
+
* #2352: the instance whose own channel lists a waiting card — the instance that handed it out, else its project's
|
|
13825
|
+
* lead — when that channel can be used now (a fleet instance of a live world, with a topic or room). undefined: its
|
|
13826
|
+
* assignee's world's General, as before.
|
|
13827
|
+
*/
|
|
13828
|
+
needsYouLane(card, leads) {
|
|
13829
|
+
const usable = (name) => {
|
|
13830
|
+
if (!name || !this.fleetConfig?.instances || !Object.hasOwn(this.fleetConfig.instances, name))
|
|
13831
|
+
return false;
|
|
13832
|
+
const world = this.getInstanceAdapterId(name);
|
|
13833
|
+
return world !== undefined && this.worlds.has(world) && this.instanceChannelTarget(name) !== null;
|
|
13834
|
+
};
|
|
13835
|
+
const dispatcher = card.dispatcher?.kind === "instance" && card.dispatcher.name !== card.assignee ? card.dispatcher.name : null;
|
|
13836
|
+
if (dispatcher)
|
|
13837
|
+
return usable(dispatcher) ? dispatcher : undefined;
|
|
13838
|
+
const lead = card.projectId != null ? leads.get(card.projectId) : null;
|
|
13839
|
+
return usable(lead) ? lead : undefined;
|
|
13840
|
+
}
|
|
13841
|
+
/**
|
|
13842
|
+
* #2355 review: where a card's Needs you buttons belong now — read from the board and the fleet at the moment of a click
|
|
13843
|
+
* or a reply, never from the last render. null: it no longer waits for the person.
|
|
13844
|
+
*/
|
|
13845
|
+
needsYouRoute(code) {
|
|
13846
|
+
const board = this.scheduler?.db.board;
|
|
13847
|
+
const card = board?.findCard(code);
|
|
13848
|
+
if (!board || !card || !(card.status === "blocked" || (card.status === "review" && card.needsReview)))
|
|
13849
|
+
return null;
|
|
13850
|
+
const leads = new Map(board.listProjects(true).map((p) => [p.id, p.leadInstance]));
|
|
13851
|
+
const lane = this.needsYouLane(card, leads);
|
|
13852
|
+
return lane ? { lane } : {};
|
|
13853
|
+
}
|
|
13854
|
+
/**
|
|
13855
|
+
* #2246 step 3: the task board's cards that wait for the person, for Needs you — under each card's assignee, listed in
|
|
13856
|
+
* its lane's channel (#2352: the instance that handed it out, else the project's lead), else in General.
|
|
13857
|
+
*/
|
|
13858
|
+
needsYouTasks() {
|
|
13859
|
+
const board = this.scheduler?.db.board;
|
|
13860
|
+
if (!board)
|
|
13861
|
+
return [];
|
|
13862
|
+
const leads = new Map(board.listProjects(true).map((p) => [p.id, p.leadInstance]));
|
|
13863
|
+
return board.waitingForPerson().map(({ card, since }) => {
|
|
13864
|
+
const gap = card.status === "review" ? wrapUpGap(card) : null;
|
|
13865
|
+
const lane = this.needsYouLane(card, leads);
|
|
13866
|
+
return {
|
|
13867
|
+
uuid: card.uuid, code: card.code, title: card.title, status: card.status, version: card.version,
|
|
13868
|
+
assignee: card.assignee, since: Date.parse(since) || 0, ...(gap ? { gap } : {}), ...(lane ? { lane } : {}),
|
|
13869
|
+
note: card.status === "blocked" ? card.blockedReason : gap ? t("needs.task_gap", [
|
|
13870
|
+
gap.unchecked ? t("needs.task_gap_unchecked", gap.unchecked) : "", gap.noResult ? t("needs.task_gap_no_result") : "",
|
|
13871
|
+
].filter(Boolean).join(t("needs.task_gap_join"))) : "",
|
|
13872
|
+
};
|
|
13873
|
+
});
|
|
13874
|
+
}
|
|
13875
|
+
/**
|
|
13876
|
+
* #2246 step 3: the person's move on a card from a chat's Needs you list — a fleet admin of that world (checked by the
|
|
13877
|
+
* hub), acting as the person, through the board's rules, at the version the button showed.
|
|
13878
|
+
*/
|
|
13879
|
+
taskActionFromChat(action) {
|
|
13880
|
+
const board = this.scheduler?.db.board;
|
|
13881
|
+
if (!board)
|
|
13882
|
+
return { ok: false, message: t("needs.task_board_closed") };
|
|
13883
|
+
const person = { kind: "person", name: action.by.username || `${action.by.platform}:${action.by.userId}` };
|
|
13884
|
+
try {
|
|
13885
|
+
if (action.verb === "confirm")
|
|
13886
|
+
board.moveCard(person, action.code, "done", { version: action.version });
|
|
13887
|
+
else if (action.verb === "back")
|
|
13888
|
+
board.moveCard(person, action.code, "claimed", { version: action.version, reason: action.text });
|
|
13889
|
+
else
|
|
13890
|
+
board.moveCard(person, action.code, "claimed", { version: action.version, comment: action.text });
|
|
13891
|
+
const done = { confirm: "needs.task_confirmed", back: "needs.task_sent_back", answer: "needs.task_answered" }[action.verb];
|
|
13892
|
+
this.logger.info({ code: action.code, verb: action.verb, by: `${action.by.platform}:${action.by.userId}` }, "Task board: the person moved a card from the chat");
|
|
13893
|
+
return { ok: true, message: t(done, action.code) };
|
|
13894
|
+
}
|
|
13895
|
+
catch (err) {
|
|
13896
|
+
if (err instanceof TaskBoardError && err.code === "conflict")
|
|
13897
|
+
return { ok: false, message: t("needs.task_changed", action.code) };
|
|
13898
|
+
return { ok: false, message: t("needs.task_refused", action.code, err.message) };
|
|
13899
|
+
}
|
|
13900
|
+
}
|
|
13683
13901
|
/** The prompts open on the dashboard right now (a page that loads after one was posted asks for them). */
|
|
13684
13902
|
listWebPrompts() {
|
|
13685
13903
|
const open = [];
|
|
@@ -16338,6 +16556,7 @@ export class FleetManager {
|
|
|
16338
16556
|
channel: m.channel,
|
|
16339
16557
|
from: m.from,
|
|
16340
16558
|
replyTo: m.replyTo,
|
|
16559
|
+
cards: m.cards,
|
|
16341
16560
|
});
|
|
16342
16561
|
broadcastSseEvent(this.sseClients, event, recorded, onError, this.webChatHistory.cursorOf(recorded));
|
|
16343
16562
|
this.searchIndex?.record(chatDoc(recorded));
|
|
@@ -16369,11 +16588,25 @@ export class FleetManager {
|
|
|
16369
16588
|
isInstance: (name) => this.fleetConfig?.instances?.[name] != null,
|
|
16370
16589
|
holds: (wake) => db.board.wakeHolds(wake),
|
|
16371
16590
|
card: (uuid) => db.board.findCard(uuid),
|
|
16591
|
+
// #2352: the card's Needs you buttons are in this instance's own channel (it is the card's lane).
|
|
16592
|
+
buttonsHere: (instance, uuid) => {
|
|
16593
|
+
// #2355 review: no live message, no buttons — the old words then.
|
|
16594
|
+
if (this.fleetConfig?.needs_you?.live_message === false)
|
|
16595
|
+
return false;
|
|
16596
|
+
const card = db.board.findCard(uuid);
|
|
16597
|
+
if (!card)
|
|
16598
|
+
return false;
|
|
16599
|
+
const leads = new Map(db.board.listProjects(true).map((p) => [p.id, p.leadInstance]));
|
|
16600
|
+
return this.needsYouLane(card, leads) === instance;
|
|
16601
|
+
},
|
|
16372
16602
|
log: (level, message, extra) => this.logger[level](extra ?? {}, message),
|
|
16373
16603
|
});
|
|
16374
16604
|
db.board.onChange(({ rev, ids, projects, wakes }) => {
|
|
16375
16605
|
this.emitSseEvent("tasks", { rev, ids, projects });
|
|
16376
16606
|
waker.note(wakes);
|
|
16607
|
+
// step 3: cards waiting for the person are Needs you items; #2355 review: a project's lead routes them too.
|
|
16608
|
+
if (ids.length || projects)
|
|
16609
|
+
this.needsYou?.poke();
|
|
16377
16610
|
});
|
|
16378
16611
|
}
|
|
16379
16612
|
/** A task board notice, as AgEnD's own (not a person's words): through the delivery outbox, held until delivered. */
|
|
@@ -16587,6 +16820,27 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
16587
16820
|
this.syncRoleSkills(workDir, backend, "general", knowledgeDir);
|
|
16588
16821
|
this.logger.debug({ knowledgeDir, workDir, backend }, "Synced general knowledge files");
|
|
16589
16822
|
}
|
|
16823
|
+
/** Update only AgEnD-managed skill copies; no launch or restart is needed for an experimental switch. */
|
|
16824
|
+
canvasWorkerSettingChanged() {
|
|
16825
|
+
this.refreshExperimentalSkills();
|
|
16826
|
+
this.emitSseEvent("status", this.getUiStatus());
|
|
16827
|
+
}
|
|
16828
|
+
refreshExperimentalSkills() {
|
|
16829
|
+
for (const [name, daemon] of this.daemons) {
|
|
16830
|
+
try {
|
|
16831
|
+
const cfg = daemon.getConfigSnapshot();
|
|
16832
|
+
const backend = cfg.backend ?? this.fleetConfig?.defaults?.backend ?? "claude-code";
|
|
16833
|
+
const workDir = this.resolveKnowledgeWorkDir(cfg.working_directory, backend, name);
|
|
16834
|
+
const classic = this.classicChannels?.getAll().some(ch => ch.instanceName === name) === true;
|
|
16835
|
+
if (classic && !insideManagedWorkspaces(workDir))
|
|
16836
|
+
continue;
|
|
16837
|
+
this.syncRoleSkills(workDir, backend, classic ? "classic" : cfg.general_topic ? "general" : "worker");
|
|
16838
|
+
}
|
|
16839
|
+
catch (err) {
|
|
16840
|
+
this.logger.warn({ name, err }, "Could not refresh experimental skill visibility");
|
|
16841
|
+
}
|
|
16842
|
+
}
|
|
16843
|
+
}
|
|
16590
16844
|
/** Publish only the bundled skills eligible for an instance role. */
|
|
16591
16845
|
syncRoleSkills(workDir, backend, role, knowledgeDir) {
|
|
16592
16846
|
const skillSegments = FleetManager.SKILLS_DIR_SEGMENTS[backend];
|
|
@@ -16751,7 +17005,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
16751
17005
|
continue;
|
|
16752
17006
|
}
|
|
16753
17007
|
managed.push(name);
|
|
16754
|
-
const newContent =
|
|
17008
|
+
const newContent = bundledSkillContents(join(srcSkills, name), this.canvasWorkerEnabled());
|
|
16755
17009
|
if (refuse(dest))
|
|
16756
17010
|
continue;
|
|
16757
17011
|
try {
|
|
@@ -19274,6 +19528,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
19274
19528
|
this.clearStatuslineWatchers();
|
|
19275
19529
|
this.costGuard?.stop();
|
|
19276
19530
|
this.dailySummary?.stop();
|
|
19531
|
+
this.taskBoardSummary?.stop();
|
|
19277
19532
|
this.dailyTipScheduler?.stop();
|
|
19278
19533
|
this.dailyTipScheduler = null;
|
|
19279
19534
|
if (this.updateCheckTimer) {
|
|
@@ -19610,6 +19865,9 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
19610
19865
|
// mark every row done and tell the user "changes applied".
|
|
19611
19866
|
return { rejected: why };
|
|
19612
19867
|
}
|
|
19868
|
+
if (canvasWorkerEnabled(oldConfig?.web) !== this.canvasWorkerEnabled()) {
|
|
19869
|
+
this.canvasWorkerSettingChanged();
|
|
19870
|
+
}
|
|
19613
19871
|
// Classic behavior settings share the fleet defaults but are not entries
|
|
19614
19872
|
// in fleet.yaml. Snapshot the old effective chain before reloading the
|
|
19615
19873
|
// Classic file so SIGHUP can hot-apply either source without waiting for
|
|
@@ -19859,6 +20117,20 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
19859
20117
|
return false;
|
|
19860
20118
|
}
|
|
19861
20119
|
/** Non-null when startup found the running config and fleet.yaml disagreeing. */
|
|
20120
|
+
/**
|
|
20121
|
+
* #2346 (settings schema): whether a connection added now would start at once — what canHotAddConnections would decide
|
|
20122
|
+
* for the current config with one more connection appended: another one is running, and nothing else this process took
|
|
20123
|
+
* at startup has changed since (a pending locale or access change, say, needs AgEnD restarted anyway).
|
|
20124
|
+
*/
|
|
20125
|
+
connectionsStartLive() {
|
|
20126
|
+
if (!this.fleetConfig)
|
|
20127
|
+
return false;
|
|
20128
|
+
const current = this.fleetConfig.channels ?? (this.fleetConfig.channel ? [this.fleetConfig.channel] : []);
|
|
20129
|
+
const probe = { id: "\u0000probe", type: "telegram", mode: "topic", bot_token_env: "\u0000PROBE" };
|
|
20130
|
+
const next = { ...this.fleetConfig, channels: [...current, probe] };
|
|
20131
|
+
delete next.channel;
|
|
20132
|
+
return this.canHotAddConnections(next);
|
|
20133
|
+
}
|
|
19862
20134
|
fleetSignatureMismatchKeys() {
|
|
19863
20135
|
return this.fleetSignatureMismatch;
|
|
19864
20136
|
}
|
|
@@ -20113,9 +20385,10 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
20113
20385
|
const tunnel = spec.kind === "tunnel_token";
|
|
20114
20386
|
if (!tunnel && !spec.verifier)
|
|
20115
20387
|
return { ok: false, status: "unsupported_verifier", error: "this provider has no supported verifier" };
|
|
20116
|
-
|
|
20117
|
-
|
|
20118
|
-
|
|
20388
|
+
const secret = tunnel && !input.clear ? normalizePublicTunnelToken(input.secret) : input.secret;
|
|
20389
|
+
if (input.clear && (!tunnel || input.secret !== "")
|
|
20390
|
+
|| !input.clear && (!secret || secret.length > 4096 || /[\r\n\0]/.test(secret) || /[^\x20-\x7e]/.test(secret))) {
|
|
20391
|
+
return { ok: false, status: "invalid", error: tunnel ? PUBLIC_TUNNEL_TOKEN_HINT : "secret is invalid" };
|
|
20119
20392
|
}
|
|
20120
20393
|
const challengeKey = `${input.sessionBinding}:api_key:${spec.id}:${spec.envKey}:${input.idempotencyKey}`;
|
|
20121
20394
|
const existingId = this.providerSecretChallengesByKey.get(challengeKey);
|
|
@@ -20143,7 +20416,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
20143
20416
|
operation: "provider-secret.apply",
|
|
20144
20417
|
idempotencyKey: input.idempotencyKey,
|
|
20145
20418
|
expiresAt, deadline: performance.now() + SECRET_CHALLENGE_TTL_MS,
|
|
20146
|
-
secret:
|
|
20419
|
+
secret: secret,
|
|
20147
20420
|
};
|
|
20148
20421
|
this.providerSecretChallenges.set(challenge.id, challenge);
|
|
20149
20422
|
this.providerSecretChallengesByKey.set(challengeKey, challenge.id);
|
|
@@ -21287,7 +21560,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
21287
21560
|
// up to 15s each, and on a beta build BOTH ran — 30s with no WATCHDOG ping,
|
|
21288
21561
|
// past WatchdogSec's half-interval and enough for systemd to SIGABRT the fleet
|
|
21289
21562
|
// for a background version check.
|
|
21290
|
-
const { execFile } = await import("
|
|
21563
|
+
const { execFile } = await import("./child-process.js");
|
|
21291
21564
|
const { promisify } = await import("node:util");
|
|
21292
21565
|
const execFileP = promisify(execFile);
|
|
21293
21566
|
const npmVersion = async (spec) => {
|
|
@@ -21579,6 +21852,11 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
21579
21852
|
return;
|
|
21580
21853
|
}
|
|
21581
21854
|
const requestPath = new URL(req.url ?? "/", `http://localhost:${port}`).pathname;
|
|
21855
|
+
if (!this.canvasWorkerEnabled() && isCanvasWorkerHttpPath(requestPath)) {
|
|
21856
|
+
res.writeHead(403, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
21857
|
+
res.end(JSON.stringify({ error: "canvas_worker_experimental", message: CANVAS_WORKER_EXPERIMENTAL_MESSAGE }));
|
|
21858
|
+
return;
|
|
21859
|
+
}
|
|
21582
21860
|
if (this.effectBootstrap.handle(req, res))
|
|
21583
21861
|
return;
|
|
21584
21862
|
// Branding is public, after Host admission: automatic favicon requests
|
|
@@ -22028,6 +22306,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
22028
22306
|
return;
|
|
22029
22307
|
}
|
|
22030
22308
|
const listener = createPreviewListener({ settings, healthPort: boundPort, config: this.fleetConfig,
|
|
22309
|
+
canvasWorkerEnabled: () => this.canvasWorkerEnabled(),
|
|
22031
22310
|
// #1723: with a web.preview_origin, the open public link's dashboard frames this listener's preview too.
|
|
22032
22311
|
extraOrigins: () => { const o = settings.origin ? this.publicWebLink?.openDashboardOrigin() : null; return o ? [o] : []; } });
|
|
22033
22312
|
this.previewListener = listener;
|
|
@@ -22205,7 +22484,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
22205
22484
|
return {};
|
|
22206
22485
|
} })(),
|
|
22207
22486
|
// #2118: only this Chat's placement content; guest projection drops global revisions.
|
|
22208
|
-
effect_chat_v: createHash("sha256").update(JSON.stringify(this.themeStore.get(name).placements.effects)).digest("hex").slice(0, 32),
|
|
22487
|
+
effect_chat_v: this.canvasWorkerEnabled() ? createHash("sha256").update(JSON.stringify(this.themeStore.get(name).placements.effects)).digest("hex").slice(0, 32) : null,
|
|
22209
22488
|
// #1958: the identity colour's tiers (the page resolves them: the agent's only while it shows agents' themes).
|
|
22210
22489
|
color: { ...(colors.get(name)?.color ? { person: colors.get(name).color } : {}), ...(image.agentColor ? { agent: image.agentColor } : {}),
|
|
22211
22490
|
...(image.roleColor ? { role: image.roleColor } : {}), auto: autoIdentityColor(name) },
|
|
@@ -22232,6 +22511,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
22232
22511
|
};
|
|
22233
22512
|
});
|
|
22234
22513
|
return {
|
|
22514
|
+
canvas_worker: this.canvasWorkerEnabled(),
|
|
22235
22515
|
instances,
|
|
22236
22516
|
publicLink: this.getPublicWebStatus(),
|
|
22237
22517
|
uptime: Math.floor((Date.now() - this.startedAt) / 1000),
|