@songsid/agend 2.3.1 → 2.3.2-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/THIRD_PARTY_NOTICES.md +40 -0
- package/dist/agent-cli.js +21 -2
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.js +5 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/antigravity-usage-scan.d.ts +58 -0
- package/dist/antigravity-usage-scan.js +257 -0
- package/dist/antigravity-usage-scan.js.map +1 -0
- package/dist/antigravity-usage-worker.d.ts +1 -0
- package/dist/antigravity-usage-worker.js +10 -0
- package/dist/antigravity-usage-worker.js.map +1 -0
- package/dist/auth-api.js +4 -0
- package/dist/auth-api.js.map +1 -1
- package/dist/authority/authority-state.d.ts +4 -0
- package/dist/authority/authority-state.js +16 -10
- package/dist/authority/authority-state.js.map +1 -1
- package/dist/authority/session-authorizations.d.ts +7 -1
- package/dist/authority/session-authorizations.js +6 -4
- package/dist/authority/session-authorizations.js.map +1 -1
- package/dist/authority/web-authority.d.ts +19 -6
- package/dist/authority/web-authority.js +22 -8
- package/dist/authority/web-authority.js.map +1 -1
- package/dist/backend/kiro-identity.d.ts +64 -0
- package/dist/backend/kiro-identity.js +101 -0
- package/dist/backend/kiro-identity.js.map +1 -1
- package/dist/backend/kiro-retired-model.d.ts +62 -0
- package/dist/backend/kiro-retired-model.js +160 -0
- package/dist/backend/kiro-retired-model.js.map +1 -0
- package/dist/backend/kiro.d.ts +71 -2
- package/dist/backend/kiro.js +352 -11
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/types.d.ts +59 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/backend-signin-guide.d.ts +1 -0
- package/dist/backend-signin-guide.js +9 -0
- package/dist/backend-signin-guide.js.map +1 -0
- package/dist/backend-signin-pending.d.ts +10 -0
- package/dist/backend-signin-pending.js +44 -0
- package/dist/backend-signin-pending.js.map +1 -0
- package/dist/browser-backend-signins.d.ts +56 -0
- package/dist/browser-backend-signins.js +189 -0
- package/dist/browser-backend-signins.js.map +1 -0
- package/dist/cache-ledger.d.ts +38 -2
- package/dist/cache-ledger.js +133 -8
- package/dist/cache-ledger.js.map +1 -1
- package/dist/cache-prices.d.ts +2 -1
- package/dist/cache-prices.js +5 -2
- package/dist/cache-prices.js.map +1 -1
- package/dist/cache-service.d.ts +68 -0
- package/dist/cache-service.js +355 -13
- package/dist/cache-service.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +13 -0
- package/dist/channel/adapters/discord.js +103 -16
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/mcp-tools.js +34 -7
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/tool-router.js +2 -1
- package/dist/channel/tool-router.js.map +1 -1
- package/dist/channel/types.d.ts +11 -0
- package/dist/classic-channel-manager.d.ts +11 -0
- package/dist/classic-channel-manager.js +39 -1
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli.js +21 -2
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.d.ts +7 -1
- package/dist/config-validator.js +92 -12
- package/dist/config-validator.js.map +1 -1
- package/dist/config.d.ts +19 -0
- package/dist/config.js +31 -1
- package/dist/config.js.map +1 -1
- package/dist/cpu-quota.d.ts +23 -0
- package/dist/cpu-quota.js +86 -0
- package/dist/cpu-quota.js.map +1 -0
- package/dist/daemon.d.ts +57 -3
- package/dist/daemon.js +330 -50
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard-permissions.d.ts +20 -6
- package/dist/dashboard-permissions.js +58 -9
- package/dist/dashboard-permissions.js.map +1 -1
- package/dist/delivery-outbox.d.ts +12 -1
- package/dist/delivery-outbox.js +67 -15
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/doctor.d.ts +3 -0
- package/dist/doctor.js +11 -1
- package/dist/doctor.js.map +1 -1
- package/dist/event-log.d.ts +5 -0
- package/dist/event-log.js +13 -0
- package/dist/event-log.js.map +1 -1
- package/dist/fleet-manager.d.ts +212 -0
- package/dist/fleet-manager.js +914 -111
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-process-handlers.d.ts +1 -0
- package/dist/fleet-process-handlers.js +9 -1
- package/dist/fleet-process-handlers.js.map +1 -1
- package/dist/forged-envelope-scan.d.ts +9 -0
- package/dist/forged-envelope-scan.js +30 -5
- package/dist/forged-envelope-scan.js.map +1 -1
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +5 -2
- package/dist/general-knowledge/skills/kiro-engine-migration/SKILL.md +14 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +28 -5
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +31 -8
- package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +138 -59
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +145 -61
- package/dist/guest-seen-people.d.ts +38 -0
- package/dist/guest-seen-people.js +78 -0
- package/dist/guest-seen-people.js.map +1 -0
- package/dist/instance-config-impact.js +14 -6
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +20 -0
- package/dist/instance-lifecycle.js +71 -13
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/kiro-db-reader.d.ts +5 -0
- package/dist/kiro-db-reader.js +18 -0
- package/dist/kiro-db-reader.js.map +1 -1
- package/dist/kiro-engine-status.d.ts +2 -0
- package/dist/kiro-engine-status.js +9 -0
- package/dist/kiro-engine-status.js.map +1 -1
- package/dist/kiro-migrate.d.ts +49 -0
- package/dist/kiro-migrate.js +105 -0
- package/dist/kiro-migrate.js.map +1 -0
- package/dist/kiro-transcript-lane.d.ts +2 -0
- package/dist/kiro-transcript-lane.js.map +1 -1
- package/dist/kiro-transcript-worker.js +1 -0
- package/dist/kiro-transcript-worker.js.map +1 -1
- package/dist/kiro-tui-forensic.d.ts +7 -0
- package/dist/kiro-tui-forensic.js +110 -0
- package/dist/kiro-tui-forensic.js.map +1 -0
- package/dist/kiro-usage-run.d.ts +7 -0
- package/dist/kiro-usage-run.js +39 -0
- package/dist/kiro-usage-run.js.map +1 -0
- package/dist/kiro-usage-scan.d.ts +49 -0
- package/dist/kiro-usage-scan.js +223 -0
- package/dist/kiro-usage-scan.js.map +1 -0
- package/dist/kiro-usage-worker.d.ts +1 -0
- package/dist/kiro-usage-worker.js +10 -0
- package/dist/kiro-usage-worker.js.map +1 -0
- package/dist/kiro-usage.d.ts +58 -0
- package/dist/kiro-usage.js +310 -0
- package/dist/kiro-usage.js.map +1 -0
- package/dist/locale.js +120 -12
- package/dist/locale.js.map +1 -1
- package/dist/log-service.js +1 -1
- package/dist/log-service.js.map +1 -1
- package/dist/login-url.d.ts +4 -0
- package/dist/login-url.js +22 -0
- package/dist/login-url.js.map +1 -0
- package/dist/mac-auto-restore.d.ts +117 -0
- package/dist/mac-auto-restore.js +294 -0
- package/dist/mac-auto-restore.js.map +1 -0
- package/dist/needs-you-hub.d.ts +2 -1
- package/dist/needs-you-hub.js +1 -1
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you.d.ts +8 -2
- package/dist/needs-you.js +6 -0
- package/dist/needs-you.js.map +1 -1
- package/dist/notice-delivery.d.ts +16 -0
- package/dist/notice-delivery.js +35 -0
- package/dist/notice-delivery.js.map +1 -0
- package/dist/office-share-page.d.ts +16 -0
- package/dist/office-share-page.js +85 -0
- package/dist/office-share-page.js.map +1 -0
- package/dist/office-share-service.d.ts +91 -0
- package/dist/office-share-service.js +176 -0
- package/dist/office-share-service.js.map +1 -0
- package/dist/office-share.d.ts +258 -0
- package/dist/office-share.js +549 -0
- package/dist/office-share.js.map +1 -0
- package/dist/opencode-usage-scan.d.ts +28 -0
- package/dist/opencode-usage-scan.js +92 -0
- package/dist/opencode-usage-scan.js.map +1 -0
- package/dist/opencode-usage-worker.d.ts +1 -0
- package/dist/opencode-usage-worker.js +10 -0
- package/dist/opencode-usage-worker.js.map +1 -0
- package/dist/outbound-handlers.js +22 -2
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +28 -2
- package/dist/outbound-schemas.js +29 -2
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/public-web-gateway.js +16 -2
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/quickstart-api.js +13 -2
- package/dist/quickstart-api.js.map +1 -1
- package/dist/quickstart.js +2 -1
- package/dist/quickstart.js.map +1 -1
- package/dist/remote-card-answer.d.ts +83 -0
- package/dist/remote-card-answer.js +208 -0
- package/dist/remote-card-answer.js.map +1 -0
- package/dist/reply-triggers.d.ts +2 -0
- package/dist/reply-triggers.js +3 -0
- package/dist/reply-triggers.js.map +1 -0
- package/dist/settings-api.d.ts +28 -0
- package/dist/settings-api.js +148 -17
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +54 -17
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-effect.js +12 -3
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-instance-patch.js +4 -1
- package/dist/settings-instance-patch.js.map +1 -1
- package/dist/setup-form.d.ts +1 -1
- package/dist/setup-form.js +55 -6
- package/dist/setup-form.js.map +1 -1
- package/dist/setup-host.d.ts +9 -0
- package/dist/setup-host.js +127 -6
- package/dist/setup-host.js.map +1 -1
- package/dist/setup-signin-assets.d.ts +2 -0
- package/dist/setup-signin-assets.js +6 -0
- package/dist/setup-signin-assets.js.map +1 -0
- package/dist/setup-wizard.js +2 -0
- package/dist/setup-wizard.js.map +1 -1
- package/dist/start-attempt.d.ts +9 -5
- package/dist/start-attempt.js +27 -8
- package/dist/start-attempt.js.map +1 -1
- package/dist/start-failure-chat.d.ts +4 -0
- package/dist/start-failure-chat.js +23 -6
- package/dist/start-failure-chat.js.map +1 -1
- package/dist/start-failure-notice.d.ts +4 -0
- package/dist/start-failure-notice.js +8 -0
- package/dist/start-failure-notice.js.map +1 -1
- package/dist/start-failure.d.ts +25 -0
- package/dist/start-failure.js +75 -2
- package/dist/start-failure.js.map +1 -1
- package/dist/structured-signin-transport.d.ts +8 -0
- package/dist/structured-signin-transport.js +79 -0
- package/dist/structured-signin-transport.js.map +1 -0
- package/dist/structured-signin.d.ts +131 -0
- package/dist/structured-signin.js +388 -0
- package/dist/structured-signin.js.map +1 -0
- package/dist/tool-permissions.js +12 -0
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.js +6 -2
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-sources.d.ts +26 -2
- package/dist/transcript-sources.js +103 -40
- package/dist/transcript-sources.js.map +1 -1
- package/dist/turn-reply-guard.d.ts +25 -0
- package/dist/turn-reply-guard.js +111 -12
- package/dist/turn-reply-guard.js.map +1 -1
- package/dist/types.d.ts +46 -9
- package/dist/ui/backend-signin.js +68 -12
- package/dist/ui/chat-ambient.js +1 -1
- package/dist/ui/chat-effects.js +4 -4
- package/dist/ui/chat-render.js +5 -2
- package/dist/ui/chat-strings.js +4 -4
- package/dist/ui/chat-thread.js +2 -2
- package/dist/ui/first-run.js +2 -2
- package/dist/ui/fleet-canvas-view.js +114 -0
- package/dist/ui/fleet-canvas.js +15 -77
- package/dist/ui/fleet-office-editor.js +94 -14
- package/dist/ui/fleet-office-layout.js +235 -0
- package/dist/ui/fleet-office-model.js +12 -6
- package/dist/ui/fleet-office-overview.js +167 -0
- package/dist/ui/fleet-office-pack.d.ts +44 -0
- package/dist/ui/fleet-office-pack.js +352 -37
- package/dist/ui/fleet-office-skin.js +220 -36
- package/dist/ui/fleet-office-store.js +112 -7
- package/dist/ui/fleet-office-view.js +155 -0
- package/dist/ui/fleet-office-zones.js +264 -42
- package/dist/ui/fleet-office.js +957 -160
- package/dist/ui/fleet-usage.js +26 -21
- package/dist/ui/office-share-dialog.js +145 -0
- package/dist/ui/office-share-page.js +164 -0
- package/dist/ui/panel-chat.js +9 -2
- package/dist/ui/panel-details.js +2 -2
- package/dist/ui/panel-fleet.js +11 -77
- package/dist/ui/panel-log.js +4 -4
- package/dist/ui/panel-needs.js +4 -4
- package/dist/ui/panel-settings.js +608 -167
- package/dist/ui/search-dialog.js +26 -7
- package/dist/ui/settings-appearance.js +82 -29
- package/dist/ui/settings-apply.js +42 -7
- package/dist/ui/settings-dialogs.js +76 -23
- package/dist/ui/settings-draft.js +51 -0
- package/dist/ui/settings-guests.js +255 -79
- package/dist/ui/settings-learn.js +2 -2
- package/dist/ui/settings-model.js +16 -6
- package/dist/ui/settings-registry.js +146 -0
- package/dist/ui/settings-strings.js +116 -44
- package/dist/ui/settings-web-authority.js +34 -23
- package/dist/ui/shared/app-i18n.js +18 -2
- package/dist/ui/shared/app-nav.js +10 -5
- package/dist/ui/shared/app-prefs.js +80 -2
- package/dist/ui/shared/app-route.js +39 -5
- package/dist/ui/shared/app-shell.js +73 -10
- package/dist/ui/shared/app-stream.js +3 -1
- package/dist/ui/shared/app.css +307 -17
- package/dist/ui/shared/app.js +3 -0
- package/dist/ui/shared/effect-stock-trust.js +1 -1
- package/dist/ui/shared/identity-color.js +4 -4
- package/dist/ui/shared/office/cozy.png +0 -0
- package/dist/ui/shared/side-panel.js +95 -0
- package/dist/ui/shared/signin.js +2 -2
- package/dist/ui/shared/ui-dialog.js +6 -1
- package/dist/ui/shared/ui-icons.js +2 -0
- package/dist/ui/shared/web-authorize.js +13 -12
- package/dist/ui/structured-signin.js +115 -0
- package/dist/usage-report.d.ts +20 -0
- package/dist/usage-report.js +54 -17
- package/dist/usage-report.js.map +1 -1
- package/dist/usage-rollup.d.ts +9 -1
- package/dist/usage-rollup.js +20 -4
- package/dist/usage-rollup.js.map +1 -1
- package/dist/web-api.d.ts +26 -2
- package/dist/web-api.js +361 -5
- package/dist/web-api.js.map +1 -1
- package/dist/web-auth.js +8 -2
- package/dist/web-auth.js.map +1 -1
- package/dist/web-backend-login.d.ts +1 -7
- package/dist/web-backend-login.js +2 -24
- package/dist/web-backend-login.js.map +1 -1
- package/dist/web-experimental.d.ts +1 -1
- package/dist/web-experimental.js +1 -1
- package/dist/web-experimental.js.map +1 -1
- package/dist/web-mod-agent.js +2 -2
- package/dist/web-mod-agent.js.map +1 -1
- package/dist/web-office-acts.d.ts +173 -0
- package/dist/web-office-acts.js +285 -0
- package/dist/web-office-acts.js.map +1 -0
- package/dist/web-office-skins.d.ts +109 -0
- package/dist/web-office-skins.js +299 -0
- package/dist/web-office-skins.js.map +1 -0
- package/dist/web-office-store.d.ts +8 -2
- package/dist/web-office-store.js +77 -14
- package/dist/web-office-store.js.map +1 -1
- package/dist/web-settings-patch.d.ts +8 -1
- package/dist/web-settings-patch.js +65 -5
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-shell-routes.d.ts +4 -2
- package/dist/web-shell-routes.js +17 -7
- package/dist/web-shell-routes.js.map +1 -1
- package/dist/working-recovery.d.ts +104 -0
- package/dist/working-recovery.js +298 -0
- package/dist/working-recovery.js.map +1 -0
- package/dist/working-state-journal.d.ts +5 -1
- package/dist/working-state-journal.js +31 -3
- package/dist/working-state-journal.js.map +1 -1
- package/package.json +3 -2
- package/templates/windows-shortcuts.ps1 +155 -140
package/dist/daemon.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { dashboardAccessOf } from "./dashboard-permissions.js";
|
|
1
2
|
import { agentWhatsNew, agendVersion, planWhatsNew, readWhatsNewRecord, recordWhatsNew, whatsNewNotice } from "./agent-whats-new.js";
|
|
2
3
|
import { join, dirname, resolve } from "node:path";
|
|
3
4
|
import { checkoutTarget, readCheckouts, releaseCandidate, reposRoot, verifyWorktree, writeCheckouts } from "./repo-worktree-guard.js";
|
|
@@ -42,11 +43,12 @@ import { parseReplyButtons, replyButtonsFallbackText } from "./reply-buttons.js"
|
|
|
42
43
|
import { HangDetector } from "./hang-detector.js";
|
|
43
44
|
import { writeSecretFile } from "./secret-file.js";
|
|
44
45
|
import { PaneWriteLock } from "./pane-write-lock.js";
|
|
46
|
+
import { answerRemoteCard } from "./remote-card-answer.js";
|
|
45
47
|
import { buildFleetInstructions } from "./instructions.js";
|
|
46
48
|
import { formatHumanInboundMessage, prepareDeliveryWire, steerInboundEnvelope, BTW_INBOUND_PREFIX, humanBodyHash, humanOwnerObjectId } from "./human-delivery-receipts.js";
|
|
47
49
|
import { formatCrossInstanceInboundMessage, renderCrossInstanceHandoffMetadata, } from "./cross-instance-envelope.js";
|
|
48
50
|
import { agendMessageInInput, bottomRowIsReady, inputAreaText, inputShowsPastedText, pastedTextSignature, pasteLeftInInput, strandedAgendMessageInInput } from "./pane-input-residue.js";
|
|
49
|
-
import { TurnReplyGuard } from "./turn-reply-guard.js";
|
|
51
|
+
import { TurnReplyGuard, formatReplyDropDetection, turnExpectsPersonReply } from "./turn-reply-guard.js";
|
|
50
52
|
import { t } from "./locale.js";
|
|
51
53
|
import { MuseUsageRelay, clearMuseUsageSnapshot } from "./muse-usage-relay.js";
|
|
52
54
|
import { deliveryContentDigest } from "./delivery-outbox.js";
|
|
@@ -86,6 +88,8 @@ const WEB_THEME_IPC = Object.freeze({
|
|
|
86
88
|
apply_web_effect: "fleet_apply_web_effect",
|
|
87
89
|
remove_web_effect: "fleet_remove_web_effect",
|
|
88
90
|
delete_web_effect: "fleet_delete_web_effect",
|
|
91
|
+
// #2578: its own character in the Office view — the args as they are (the fleet checks them).
|
|
92
|
+
office_view: "fleet_office_view", office_set: "fleet_office_set",
|
|
89
93
|
});
|
|
90
94
|
/** The largest picture any slot may take (a card slot's ceiling; the fleet checks the limit in force): read no more. */
|
|
91
95
|
const WEB_THEME_ASSET_MAX_BYTES = ASSET_UPLOAD_MAX_BYTES;
|
|
@@ -570,6 +574,8 @@ export const OWN_SWITCH_WAIT_MS = 6_000;
|
|
|
570
574
|
const OWN_SWITCH_SETTLE_MS = 3_000;
|
|
571
575
|
/** A blocking dialog still on screen this long after first being seen is reported for a human. */
|
|
572
576
|
const DIALOG_PARKED_NOTIFY_MS = 60_000;
|
|
577
|
+
/** #2675: a card a person can answer from the chat is posted once it has been on screen, unchanged, this long. */
|
|
578
|
+
const REMOTE_CARD_STABLE_MS = 1_500;
|
|
573
579
|
/** #1691: a prompt no table knows must sit unchanged this long (monotonic) before it holds deliveries and is reported. */
|
|
574
580
|
export const UNRECOGNISED_PROMPT_STABLE_MS = 60_000;
|
|
575
581
|
/** #1691: the rows under a prompt a backend without a composer reader shows its ready chrome in (status bar, hint). */
|
|
@@ -1245,6 +1251,8 @@ export class Daemon extends EventEmitter {
|
|
|
1245
1251
|
* skipResume — the #1209 gate reads this, never the resettable flag.
|
|
1246
1252
|
*/
|
|
1247
1253
|
bootSkippedResume = false;
|
|
1254
|
+
recoveryCrashLoopFresh = false;
|
|
1255
|
+
recoveryFreshChoice = false;
|
|
1248
1256
|
startupAborted = false;
|
|
1249
1257
|
/** First time the current on-screen blocking dialog was seen (0 = none); drives the parked report. */
|
|
1250
1258
|
dialogParkedSince = 0;
|
|
@@ -1252,6 +1260,12 @@ export class Daemon extends EventEmitter {
|
|
|
1252
1260
|
dialogParkedKey = null;
|
|
1253
1261
|
dialogParkedInteractionEpisode = null;
|
|
1254
1262
|
dialogParkedReported = false;
|
|
1263
|
+
/** #2675: the card on screen that a person can answer from the chat — what it asks, since when, and whether it was posted. */
|
|
1264
|
+
remoteCardShown = null;
|
|
1265
|
+
/** #2675: when (performance.now, taken before its capture) the newest read the card tracker used was started. */
|
|
1266
|
+
remoteCardReadAt = -Infinity;
|
|
1267
|
+
/** #2675: an answer from the chat is being keyed in (one at a time). */
|
|
1268
|
+
remoteCardAnswering = false;
|
|
1255
1269
|
/**
|
|
1256
1270
|
* #1691: the prompt at the bottom of the pane that no backend table knows — its block, since when (performance.now)
|
|
1257
1271
|
* it has been unchanged, and whether that has lasted UNRECOGNISED_PROMPT_STABLE_MS. Updated by the runtime scan.
|
|
@@ -1747,6 +1761,11 @@ export class Daemon extends EventEmitter {
|
|
|
1747
1761
|
return `${ep.type}:${ep.pattern.source}`;
|
|
1748
1762
|
}
|
|
1749
1763
|
/** Read the occurrence baseline so a lifecycle-owned restart can carry it forward. */
|
|
1764
|
+
/** #2648: kiro only — what a model error on the current launch should do (see kiro-retired-model.ts). */
|
|
1765
|
+
kiroModelError() {
|
|
1766
|
+
const backend = this.backend;
|
|
1767
|
+
return typeof backend?.onModelError === "function" ? backend.onModelError() : null;
|
|
1768
|
+
}
|
|
1750
1769
|
getErrorPatternOccurrenceCount(type, pattern) {
|
|
1751
1770
|
return this.lastErrorCount.get(`${type}:${pattern.source}`) ?? 0;
|
|
1752
1771
|
}
|
|
@@ -1823,6 +1842,9 @@ export class Daemon extends EventEmitter {
|
|
|
1823
1842
|
this.spawnGate = spawnGate;
|
|
1824
1843
|
this.stormWindow = stormWindow;
|
|
1825
1844
|
this.backendOutage = backendOutage;
|
|
1845
|
+
// The live process owns its adopted configuration. Fleet defaults may be re-resolved in place after a save;
|
|
1846
|
+
// sharing that object would activate cold changes before the normal restart/apply boundary.
|
|
1847
|
+
this.config = structuredClone(config);
|
|
1826
1848
|
if (!rootLogger)
|
|
1827
1849
|
throw new Error("Daemon requires a shared root logger");
|
|
1828
1850
|
this.runtimeIdentity ??= {
|
|
@@ -2203,6 +2225,7 @@ export class Daemon extends EventEmitter {
|
|
|
2203
2225
|
}
|
|
2204
2226
|
async startAdmitted() {
|
|
2205
2227
|
this.assertSessionOomAdmission();
|
|
2228
|
+
this.recoveryBootWorkPending = true;
|
|
2206
2229
|
ensureInstanceDir(this.instanceDir);
|
|
2207
2230
|
writeFileSync(join(this.instanceDir, "daemon.pid"), String(process.pid));
|
|
2208
2231
|
this.logger.info(`Starting ${this.name}`);
|
|
@@ -2211,6 +2234,8 @@ export class Daemon extends EventEmitter {
|
|
|
2211
2234
|
// even when the JSON is corrupt so it cannot remain forever.
|
|
2212
2235
|
const crashStatePath = join(this.instanceDir, "crash-state.json");
|
|
2213
2236
|
this.bootSkippedResume = false;
|
|
2237
|
+
this.recoveryCrashLoopFresh = false;
|
|
2238
|
+
this.recoveryFreshChoice = false;
|
|
2214
2239
|
try {
|
|
2215
2240
|
if (existsSync(crashStatePath)) {
|
|
2216
2241
|
const content = readFileSync(crashStatePath, "utf-8");
|
|
@@ -2219,6 +2244,10 @@ export class Daemon extends EventEmitter {
|
|
|
2219
2244
|
if (state?.resumeDisabled === true) {
|
|
2220
2245
|
this.skipResume = true;
|
|
2221
2246
|
this.bootSkippedResume = true;
|
|
2247
|
+
this.recoveryCrashLoopFresh = (state.reason === undefined || state.reason === "crash_loop")
|
|
2248
|
+
&& Number.isInteger(state.crashesInWindow) && state.crashesInWindow >= 3
|
|
2249
|
+
&& Number.isFinite(state.lastCrashAt) && state.lastCrashAt > 0;
|
|
2250
|
+
this.recoveryFreshChoice = !this.recoveryCrashLoopFresh;
|
|
2222
2251
|
this.logger.warn("Previous crash loop detected — starting without resume");
|
|
2223
2252
|
}
|
|
2224
2253
|
}
|
|
@@ -2367,12 +2396,19 @@ export class Daemon extends EventEmitter {
|
|
|
2367
2396
|
// #1209: conditional continuation for the turn interrupted by the last
|
|
2368
2397
|
// restart, if the gates allow. Detached like the warmup notice; the gate
|
|
2369
2398
|
// consumes its one-shot marker first and never throws into boot.
|
|
2370
|
-
|
|
2399
|
+
const legacyResume = this.maybeResumeInterruptedTurn().catch(() => "unknown");
|
|
2371
2400
|
// Warmup: wait for CLI idle, then trigger steering reload — but only when
|
|
2372
2401
|
// the instructions actually changed since the agent last saw them.
|
|
2373
2402
|
// Skipping the no-op reload saves 10-30s of agent time on every restart
|
|
2374
2403
|
// where instructions are unchanged.
|
|
2375
|
-
|
|
2404
|
+
const warmup = this.runWarmupInstructionNotice();
|
|
2405
|
+
const recoveryOwner = JSON.stringify(this.interactionOwner());
|
|
2406
|
+
void Promise.allSettled([legacyResume, warmup]).then(() => {
|
|
2407
|
+
if (JSON.stringify(this.interactionOwner()) === recoveryOwner) {
|
|
2408
|
+
this.recoveryBootWorkPending = false;
|
|
2409
|
+
this.recoveryBootSettled = true;
|
|
2410
|
+
}
|
|
2411
|
+
});
|
|
2376
2412
|
if (!this.config.lightweight) {
|
|
2377
2413
|
// 3. Pipe-pane for prompt detection. Rotate first so a ballooned log from a
|
|
2378
2414
|
// previous stuck splash (hundreds of MB of ANSI frames) is truncated before
|
|
@@ -2384,7 +2420,7 @@ export class Daemon extends EventEmitter {
|
|
|
2384
2420
|
// (statusline transcript); codex/kiro/opencode read their CLI's own
|
|
2385
2421
|
// conversation store via a pluggable source. Backends with no known
|
|
2386
2422
|
// source stay inert exactly as before.
|
|
2387
|
-
this.transcriptMonitor = this.buildTranscriptMonitor(createTranscriptSource(this.config.backend ?? "claude-code", this.config.working_directory, this.credentialProfileStore()));
|
|
2423
|
+
this.transcriptMonitor = this.buildTranscriptMonitor(createTranscriptSource(this.config.backend ?? "claude-code", this.config.working_directory, this.credentialProfileStore(), () => this.backend?.transcriptLaunch?.() ?? null));
|
|
2388
2424
|
const transcriptMonitor = this.transcriptMonitor;
|
|
2389
2425
|
const transcriptFence = this.launchFenceEpoch;
|
|
2390
2426
|
// Baseline in the isolate before this daemon accepts new work. A
|
|
@@ -3716,6 +3752,7 @@ export class Daemon extends EventEmitter {
|
|
|
3716
3752
|
if (!answersBackedOff)
|
|
3717
3753
|
this.dialogAnswers = null;
|
|
3718
3754
|
this.trackDialogParked(blockingSeen);
|
|
3755
|
+
this.trackRemoteCard(blockingSeen, pane, captureMono);
|
|
3719
3756
|
if (blockingSeen)
|
|
3720
3757
|
return; // held dialog: skip error checks this cycle
|
|
3721
3758
|
this.evaluateErrorPatterns(pane, patterns, readyPattern, Date.now(), busyPattern);
|
|
@@ -3974,6 +4011,7 @@ export class Daemon extends EventEmitter {
|
|
|
3974
4011
|
clearPendingDeliveries(fleetEpoch) {
|
|
3975
4012
|
// FleetManager calls this synchronously before sendEscape. Cancel only the
|
|
3976
4013
|
// current human turn; leave already-forwarded tool requests to settle.
|
|
4014
|
+
this.recoveryCancelled = true;
|
|
3977
4015
|
this.turnReplyGuard.cancelByUser();
|
|
3978
4016
|
// #1241: a cancelled turn never confirms — its next edge completes it as
|
|
3979
4017
|
// intentional. Drop a pending confirmation so it cannot promote afterwards.
|
|
@@ -3985,6 +4023,7 @@ export class Daemon extends EventEmitter {
|
|
|
3985
4023
|
this.deliveryEpoch = fleetEpoch === undefined
|
|
3986
4024
|
? this.deliveryEpoch + 1
|
|
3987
4025
|
: Math.max(this.deliveryEpoch, fleetEpoch);
|
|
4026
|
+
this.cancelCapacityNudge("the user cancelled");
|
|
3988
4027
|
this.publishWorkingState("cancelled");
|
|
3989
4028
|
this.logger.info({ deliveryEpoch: this.deliveryEpoch }, "Pending delivery queue cleared by user cancel");
|
|
3990
4029
|
}
|
|
@@ -4703,6 +4742,88 @@ export class Daemon extends EventEmitter {
|
|
|
4703
4742
|
getInteractionSnapshot() {
|
|
4704
4743
|
return this.interactionObservation.snapshot(this.interactionOwner());
|
|
4705
4744
|
}
|
|
4745
|
+
ownedWorkingSession = null;
|
|
4746
|
+
recoveryBootSettled = false;
|
|
4747
|
+
recoveryBootWorkPending = false;
|
|
4748
|
+
recoveryLegacyCovered = false;
|
|
4749
|
+
recoveryNativeWork = false;
|
|
4750
|
+
recoveryContinuationId = null;
|
|
4751
|
+
recoveryCancelled = false;
|
|
4752
|
+
workingRecoveryInputOwner = null;
|
|
4753
|
+
noteLegacyRestartReminder() { this.recoveryLegacyCovered = true; }
|
|
4754
|
+
getWorkingRecoveryState() {
|
|
4755
|
+
const observation = this.getWorkingStateObservation();
|
|
4756
|
+
const owner = JSON.stringify(observation.owner), ownsInput = this.workingRecoveryInputOwner === owner;
|
|
4757
|
+
return { owner, configuration: observation.configuration,
|
|
4758
|
+
sessionId: this.ownedWorkingSession, idle: (ownsInput || this.instanceState === "idle") && this.pasteQueueDepth === 0,
|
|
4759
|
+
paused: this.isPaused, stopped: this.isSessionOomStopped(), legacyPending: !this.recoveryBootSettled,
|
|
4760
|
+
legacyCovered: this.recoveryLegacyCovered, nativeWork: !ownsInput && this.recoveryNativeWork,
|
|
4761
|
+
freshChoice: this.recoveryFreshChoice || this.isNewSession && !this.bootSkippedResume, crashLoopFresh: this.recoveryCrashLoopFresh,
|
|
4762
|
+
cancelled: this.recoveryCancelled, background: this.workingBackgroundObservation };
|
|
4763
|
+
}
|
|
4764
|
+
/** Local system work: no user turn, channel reply target, wake or replay of an original request. */
|
|
4765
|
+
async deliverWorkingRecovery(content, delivery, admitted) {
|
|
4766
|
+
const tmux = this.tmux, owner = JSON.stringify(this.interactionOwner()), epoch = this.deliveryEpoch;
|
|
4767
|
+
const current = () => this.tmux === tmux && owner === JSON.stringify(this.interactionOwner())
|
|
4768
|
+
&& this.deliveryEpoch === epoch && !this.deliveryWritesStopping && !this.runtimeMonitorsFrozen
|
|
4769
|
+
&& !this.spawning && !this.startupAborted && !this.isPaused && !this.healthCheckPaused
|
|
4770
|
+
&& this.processStatus === "running" && this.pasteQueueDepth === 0
|
|
4771
|
+
&& (this.workingRecoveryInputOwner === owner || this.instanceState === "idle" && !this.recoveryNativeWork)
|
|
4772
|
+
&& this.recoveryBootSettled && !this.recoveryCancelled && !this.isSessionOomStopped() && admitted();
|
|
4773
|
+
let begun = false;
|
|
4774
|
+
try {
|
|
4775
|
+
await this.paneWriteLock.run(async () => {
|
|
4776
|
+
if (!tmux || !current())
|
|
4777
|
+
return;
|
|
4778
|
+
const pane = await tmux.capturePane();
|
|
4779
|
+
const input = this.backend?.readInputRow?.(pane);
|
|
4780
|
+
if (!current() || !input || input.text !== "" || input.collapsedPastes !== 0 || input.queued
|
|
4781
|
+
|| !this.paneAuthoritativelyIdle(pane) || this.inputTransientInPane(pane)
|
|
4782
|
+
|| this.backend?.backgroundWorkRunning?.(pane) === true)
|
|
4783
|
+
return;
|
|
4784
|
+
if (await tmux.getPaneInputMode?.() !== "raw" || !current())
|
|
4785
|
+
return;
|
|
4786
|
+
const evidence = await this.durableAttemptEvidence(tmux.getWindowId(), false, false);
|
|
4787
|
+
if (!current())
|
|
4788
|
+
return;
|
|
4789
|
+
const signature = this.submissionSignature(content), baseline = this.paneEvidence(pane, signature);
|
|
4790
|
+
const attempt = { ...delivery, contentSha256: deliveryContentDigest(content) };
|
|
4791
|
+
if (!this.beginDurableDelivery(attempt, evidence))
|
|
4792
|
+
return;
|
|
4793
|
+
begun = true;
|
|
4794
|
+
// Own PTY input immediately produces a working observation. It must not
|
|
4795
|
+
// cancel this same fenced write; external queue/cancel/owner guards stay.
|
|
4796
|
+
this.workingRecoveryInputOwner = owner;
|
|
4797
|
+
// Conservatively commit possible input BEFORE paste, not merely before Enter.
|
|
4798
|
+
// A process death at any later boundary must never replay this continuation.
|
|
4799
|
+
if (!this.deliveryOutbox?.markEnterStarted(delivery.deliveryId, this.bootId, delivery.attemptNo))
|
|
4800
|
+
return;
|
|
4801
|
+
if (!current() || !await tmux.pasteBuffer(content, { guard: current }) || !current())
|
|
4802
|
+
return;
|
|
4803
|
+
await new Promise(resolve => setTimeout(resolve, NORMAL_ENTER_SETTLE_MS));
|
|
4804
|
+
if (!current() || !await this.sendDeliveryEnterAfterAvailability("working-recovery", current, attempt))
|
|
4805
|
+
return;
|
|
4806
|
+
const proof = await this.confirmSubmitted(signature, baseline);
|
|
4807
|
+
const committed = this.deliveryOutbox.complete(delivery.deliveryId, this.bootId, delivery.attemptNo, proof === "submitted" ? "delivered" : "uncertain", "local continuation Enter attempted once; no replay");
|
|
4808
|
+
if (committed && proof === "submitted" && current()) {
|
|
4809
|
+
this.recoveryContinuationId = delivery.deliveryId;
|
|
4810
|
+
this.publishWorkingState("execution");
|
|
4811
|
+
}
|
|
4812
|
+
});
|
|
4813
|
+
}
|
|
4814
|
+
catch { /* The durable fence below owns ambiguity; never retry a begun write. */ }
|
|
4815
|
+
finally {
|
|
4816
|
+
if (this.workingRecoveryInputOwner === owner) {
|
|
4817
|
+
this.workingRecoveryInputOwner = null;
|
|
4818
|
+
if (this.tmux === tmux && owner === JSON.stringify(this.interactionOwner()))
|
|
4819
|
+
this.recoveryNativeWork = true;
|
|
4820
|
+
}
|
|
4821
|
+
if (begun)
|
|
4822
|
+
this.deliveryOutbox?.complete(delivery.deliveryId, this.bootId, delivery.attemptNo, "uncertain", "local continuation may have reached input; not replayed");
|
|
4823
|
+
else
|
|
4824
|
+
this.deliveryOutbox?.retryBeforeBegin(delivery.deliveryId, this.bootId, delivery.attemptNo, "working recovery prerequisite changed before input", 5_000);
|
|
4825
|
+
}
|
|
4826
|
+
}
|
|
4706
4827
|
workingTurnDelivery = null;
|
|
4707
4828
|
workingBackgroundObservation = null;
|
|
4708
4829
|
/** Pure metadata read: never discovers sessions, captures a pane or reads backend files. */
|
|
@@ -4714,7 +4835,8 @@ export class Daemon extends EventEmitter {
|
|
|
4714
4835
|
owner: this.interactionOwner(), backend,
|
|
4715
4836
|
configuration: createHash("sha256").update(JSON.stringify([backend, this.config.working_directory, profile])).digest("hex"),
|
|
4716
4837
|
executionState: this.processStatus === "running" ? this.isPaused ? "paused" : this.instanceState : null,
|
|
4717
|
-
processStatus: this.getProcessStatus(), sessionId:
|
|
4838
|
+
processStatus: this.getProcessStatus(), sessionId: this.ownedWorkingSession, backendTurnId: null,
|
|
4839
|
+
...(this.recoveryContinuationId ? { recoveryDeliveryId: this.recoveryContinuationId } : {}),
|
|
4718
4840
|
backgroundWork: this.workingBackgroundObservation,
|
|
4719
4841
|
turn: turn ? { generation: turn.generation,
|
|
4720
4842
|
deliveryId: this.workingTurnDelivery?.generation === turn.generation ? this.workingTurnDelivery.deliveryId : null,
|
|
@@ -5174,6 +5296,9 @@ export class Daemon extends EventEmitter {
|
|
|
5174
5296
|
if (unblockedTurn && unblockedTurn.phase === "awaiting")
|
|
5175
5297
|
this.maybeProxyReplyOnTurnEnd(pane);
|
|
5176
5298
|
}
|
|
5299
|
+
if (snapshot.state === "working" && this.recoveryBootSettled
|
|
5300
|
+
&& this.workingRecoveryInputOwner !== JSON.stringify(this.interactionOwner()))
|
|
5301
|
+
this.recoveryNativeWork = true;
|
|
5177
5302
|
this.instanceState = snapshot.state;
|
|
5178
5303
|
this.acceptedInstanceSnapshot = { ...snapshot };
|
|
5179
5304
|
// Reuse an already-captured pane and the backend's pure parser, never capture for the journal.
|
|
@@ -5290,14 +5415,21 @@ export class Daemon extends EventEmitter {
|
|
|
5290
5415
|
// Its late success must not re-arm the turn that the user just cancelled.
|
|
5291
5416
|
if (!this.isDeliveryEpochCurrent(deliveryEpoch))
|
|
5292
5417
|
return;
|
|
5293
|
-
// Channel turns
|
|
5294
|
-
//
|
|
5295
|
-
//
|
|
5296
|
-
//
|
|
5297
|
-
//
|
|
5298
|
-
// web-only sink), but it is a person waiting for an answer all the
|
|
5299
|
-
|
|
5418
|
+
// Channel turns started by a person. Cross-instance, schedules (silent or
|
|
5419
|
+
// not), and AgEnD system notices must not expect a reply to a person (#2544):
|
|
5420
|
+
// a proxy or recovery notice would land in the last USER topic, or flag a
|
|
5421
|
+
// heartbeat that was told not to report. A web message on a fleet with no
|
|
5422
|
+
// chat platform has no chat_id (its reply goes to the web chat through the
|
|
5423
|
+
// fleet's web-only sink), but it is a person waiting for an answer all the
|
|
5424
|
+
// same: the guard arms for it (alpha.2).
|
|
5425
|
+
if (!turnExpectsPersonReply(meta)) {
|
|
5426
|
+
this.logger.debug({
|
|
5427
|
+
source: meta.source || null,
|
|
5428
|
+
user: meta.user || null,
|
|
5429
|
+
from_instance: meta.from_instance || null,
|
|
5430
|
+
}, "Reply guard skipped — turn was not started by a person");
|
|
5300
5431
|
return;
|
|
5432
|
+
}
|
|
5301
5433
|
// The last non-empty line of what we pasted: everything on screen after it
|
|
5302
5434
|
// is the agent's own output.
|
|
5303
5435
|
const inboundMarker = deliveredText.split(/\r?\n/).map(l => l.trim()).filter(Boolean).pop();
|
|
@@ -5314,7 +5446,8 @@ export class Daemon extends EventEmitter {
|
|
|
5314
5446
|
deliveredText,
|
|
5315
5447
|
deliveredAt,
|
|
5316
5448
|
}, this.pendingWork.lastInboundTimestamp());
|
|
5317
|
-
|
|
5449
|
+
const armed = this.turnReplyGuard.snapshot();
|
|
5450
|
+
this.workingTurnDelivery = { generation: armed.generation, deliveryId: meta.delivery_id || null };
|
|
5318
5451
|
this.publishWorkingState("turn_started");
|
|
5319
5452
|
// #1209: persist the in-flight turn with a seam checkpoint. A marker
|
|
5320
5453
|
// that survives a restart is an interrupted turn by construction —
|
|
@@ -5323,7 +5456,9 @@ export class Daemon extends EventEmitter {
|
|
|
5323
5456
|
// the interruption. Reads are windowed/bounded (see turn-resume.ts).
|
|
5324
5457
|
// A resumed continuation never re-arms (P3a): episode-once means a crash
|
|
5325
5458
|
// during the continuation boots clean instead of chaining resumes.
|
|
5326
|
-
|
|
5459
|
+
// #2544: a reply that settled before this arm already answered the person —
|
|
5460
|
+
// writing a marker would re-inject a continuation for a finished turn.
|
|
5461
|
+
if (!meta.resumedContinuationOf && !armed.completionDelivered) {
|
|
5327
5462
|
const resumeBackend = this.config.backend ?? this.backend?.binaryName ?? this.runtimeIdentity?.backend;
|
|
5328
5463
|
const resumeCwd = this.config.working_directory ?? "";
|
|
5329
5464
|
writeInFlightTurnMarker(this.instanceDir, {
|
|
@@ -5371,6 +5506,7 @@ export class Daemon extends EventEmitter {
|
|
|
5371
5506
|
const marker = consumeInFlightTurnMarker(this.instanceDir);
|
|
5372
5507
|
if (!marker)
|
|
5373
5508
|
return "none";
|
|
5509
|
+
this.recoveryLegacyCovered = true;
|
|
5374
5510
|
// P1 fence: capture before any await. A cancel (deliveryEpoch),
|
|
5375
5511
|
// respawn (spawnGeneration) or stop/pause (launchFenceEpoch) during the
|
|
5376
5512
|
// grace hold must stale this gate — the push below rechecks, and the
|
|
@@ -5736,8 +5872,8 @@ export class Daemon extends EventEmitter {
|
|
|
5736
5872
|
generation: turn.generation,
|
|
5737
5873
|
reason,
|
|
5738
5874
|
recoveryStarted: true,
|
|
5875
|
+
message: formatReplyDropDetection(turn, reason),
|
|
5739
5876
|
});
|
|
5740
|
-
void this.deliverDaemonReply(t("inst.reply_drop_retrying"), "replydrop", "Reply-drop status", turn.target, true);
|
|
5741
5877
|
this.queueReplyRecoveryPrompt(turn);
|
|
5742
5878
|
}
|
|
5743
5879
|
reportReplyDropWithoutRetry(turn, reason) {
|
|
@@ -5747,8 +5883,8 @@ export class Daemon extends EventEmitter {
|
|
|
5747
5883
|
generation: turn.generation,
|
|
5748
5884
|
reason,
|
|
5749
5885
|
recoveryStarted: false,
|
|
5886
|
+
message: formatReplyDropDetection(turn, reason),
|
|
5750
5887
|
});
|
|
5751
|
-
void this.deliverDaemonReply(t("inst.reply_drop_unknown"), "replydrop", "Unconfirmed reply status", turn.target, true);
|
|
5752
5888
|
}
|
|
5753
5889
|
queueReplyRecoveryPrompt(turn) {
|
|
5754
5890
|
const deliveryEpoch = this.deliveryEpoch;
|
|
@@ -5840,13 +5976,22 @@ export class Daemon extends EventEmitter {
|
|
|
5840
5976
|
correlationId: turn.target.correlationId,
|
|
5841
5977
|
generation: turn.generation,
|
|
5842
5978
|
reason,
|
|
5979
|
+
message: formatReplyDropDetection(turn, reason),
|
|
5843
5980
|
});
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5981
|
+
this.deliverReplyDropNotice("inst.reply_drop_unrecovered", "replydropwarn", "Reply-drop recovery warning", turn, reason, true);
|
|
5982
|
+
}
|
|
5983
|
+
/** Channel notice for a reply-drop. `rateLimit` shares the per-instance cooldown (#2544). */
|
|
5984
|
+
deliverReplyDropNotice(key, kind, title, turn, reason, rateLimit) {
|
|
5985
|
+
if (rateLimit) {
|
|
5986
|
+
const now = Date.now();
|
|
5987
|
+
if (this.lastReplyDropWarningAt !== 0
|
|
5988
|
+
&& now - this.lastReplyDropWarningAt < REPLY_DROP_WARNING_COOLDOWN_MS) {
|
|
5989
|
+
this.logger.debug({ reason, kind }, "Reply-drop channel notice rate-limited");
|
|
5990
|
+
return;
|
|
5991
|
+
}
|
|
5992
|
+
this.lastReplyDropWarningAt = now;
|
|
5993
|
+
}
|
|
5994
|
+
void this.deliverDaemonReply(t(key), kind, title, turn.target, true);
|
|
5850
5995
|
}
|
|
5851
5996
|
/** Relay the pane's final text to the channel, marked as a daemon proxy reply. */
|
|
5852
5997
|
async sendProxyReply(pane, target) {
|
|
@@ -6444,6 +6589,7 @@ export class Daemon extends EventEmitter {
|
|
|
6444
6589
|
}
|
|
6445
6590
|
/** Stop every runtime poller/watcher while preserving IPC and daemon state. */
|
|
6446
6591
|
freezeRuntimeMonitors() {
|
|
6592
|
+
this.cancelCapacityNudge("the instance was stopped, paused or respawned");
|
|
6447
6593
|
this.runtimeMonitorsFrozen = true;
|
|
6448
6594
|
this.launchFenceEpoch++;
|
|
6449
6595
|
this.interactionObservation.reset(this.interactionOwner());
|
|
@@ -6542,6 +6688,10 @@ export class Daemon extends EventEmitter {
|
|
|
6542
6688
|
if (update.tool_progress === null || ["off", "standard", "verbose"].includes(String(update.tool_progress))) {
|
|
6543
6689
|
this.updateToolProgress(update.tool_progress === null ? undefined : update.tool_progress);
|
|
6544
6690
|
}
|
|
6691
|
+
if (typeof update.reply_triggers === "boolean")
|
|
6692
|
+
this.config.reply_triggers = update.reply_triggers;
|
|
6693
|
+
else if (update.reply_triggers === null)
|
|
6694
|
+
delete this.config.reply_triggers;
|
|
6545
6695
|
if (typeof update.reply_completion_guard === "boolean") {
|
|
6546
6696
|
this.config.reply_completion_guard = update.reply_completion_guard;
|
|
6547
6697
|
}
|
|
@@ -6556,6 +6706,10 @@ export class Daemon extends EventEmitter {
|
|
|
6556
6706
|
this.config.warm_cap = update.warm_cap;
|
|
6557
6707
|
else if (update.warm_cap === null)
|
|
6558
6708
|
delete this.config.warm_cap;
|
|
6709
|
+
if (typeof update.resume_interrupted_work === "boolean")
|
|
6710
|
+
this.config.resume_interrupted_work = update.resume_interrupted_work;
|
|
6711
|
+
else if (update.resume_interrupted_work === null)
|
|
6712
|
+
delete this.config.resume_interrupted_work;
|
|
6559
6713
|
const nextAutoPause = typeof update.auto_pause_after === "number"
|
|
6560
6714
|
&& Number.isFinite(update.auto_pause_after)
|
|
6561
6715
|
&& update.auto_pause_after >= 0
|
|
@@ -6591,6 +6745,10 @@ export class Daemon extends EventEmitter {
|
|
|
6591
6745
|
else if (update.board_cards_at_once === null)
|
|
6592
6746
|
delete this.config.board_cards_at_once;
|
|
6593
6747
|
// The fleet reads it at each tool call; kept current here for the next spawn's tool menu.
|
|
6748
|
+
if (typeof update.office_access === "boolean")
|
|
6749
|
+
this.config.office_access = update.office_access;
|
|
6750
|
+
else if (update.office_access === null)
|
|
6751
|
+
delete this.config.office_access;
|
|
6594
6752
|
if (update.dashboard && typeof update.dashboard === "object" && !dashboardAccessProblem(update.dashboard))
|
|
6595
6753
|
this.config.dashboard = update.dashboard;
|
|
6596
6754
|
else if (update.dashboard === null)
|
|
@@ -7820,15 +7978,18 @@ export class Daemon extends EventEmitter {
|
|
|
7820
7978
|
&& dialogs.some(other => other !== dialog && Daemon.dialogMatches(other, pane)))
|
|
7821
7979
|
continue;
|
|
7822
7980
|
this.trackDialogParked(dialog);
|
|
7981
|
+
this.trackRemoteCard(dialog, pane, probeMono);
|
|
7823
7982
|
return { state: "dialog", dialog };
|
|
7824
7983
|
}
|
|
7825
7984
|
this.trackDialogParked(null);
|
|
7985
|
+
this.trackRemoteCard(null, pane, probeMono);
|
|
7826
7986
|
return { state: "clear" };
|
|
7827
7987
|
}
|
|
7828
7988
|
/**
|
|
7829
7989
|
* Arm the "keep going" nudge for the error occurrence just reported (`pattern` must be the one the lifecycle was
|
|
7830
7990
|
* handed). The nudge is sent once, `delayMs` from now on the MONOTONIC clock (a wall-clock step changes nothing),
|
|
7831
|
-
* and only if
|
|
7991
|
+
* and only if the failed turn still owns an empty idle composer — see {@link tickCapacityNudge}.
|
|
7992
|
+
* Cosmetic footer redraws are not new work. Returns false when there is nothing to arm.
|
|
7832
7993
|
*/
|
|
7833
7994
|
armCapacityNudge(pattern, delayMs) {
|
|
7834
7995
|
const episode = this.lastErrorEpisode;
|
|
@@ -7847,9 +8008,12 @@ export class Daemon extends EventEmitter {
|
|
|
7847
8008
|
// lookahead on the counting pattern: whole-pane dedup would max at 1 and miss later hits. See #1215.)
|
|
7848
8009
|
if (!codexLiveRowMatches(episode.pane, pattern))
|
|
7849
8010
|
return false;
|
|
8011
|
+
this.cancelCapacityNudge("the instance has work of its own");
|
|
7850
8012
|
this.capacityNudge = {
|
|
7851
8013
|
pane: episode.pane, pattern, spawn: episode.spawn, fence: episode.fence,
|
|
7852
8014
|
epoch: this.deliveryEpoch, dueAt: performance.now() + delayMs,
|
|
8015
|
+
episode,
|
|
8016
|
+
promiseTarget: null,
|
|
7853
8017
|
};
|
|
7854
8018
|
this.logger.info({ delayMs }, "Model-capacity nudge armed");
|
|
7855
8019
|
return true;
|
|
@@ -7864,7 +8028,7 @@ export class Daemon extends EventEmitter {
|
|
|
7864
8028
|
const nudge = this.capacityNudge, episode = this.lastErrorEpisode;
|
|
7865
8029
|
const tmux = this.tmux, attempt = this.launchAttempt, boot = this.bootId;
|
|
7866
8030
|
this.cancelCapacityNudge("retries exhausted");
|
|
7867
|
-
if (!nudge || !episode || !tmux)
|
|
8031
|
+
if (!nudge || !episode || nudge.episode !== episode || !tmux)
|
|
7868
8032
|
return null;
|
|
7869
8033
|
const window = tmux.getWindowId?.();
|
|
7870
8034
|
const current = () => this.tmux === tmux && tmux.getWindowId?.() === window
|
|
@@ -7872,9 +8036,7 @@ export class Daemon extends EventEmitter {
|
|
|
7872
8036
|
&& episode === this.lastErrorEpisode && this.isDeliveryEpochCurrent(nudge.epoch)
|
|
7873
8037
|
&& !this.startupAborted && !this.spawning && !this.isPaused
|
|
7874
8038
|
&& this.getProcessStatus() === "running" && this.instanceState === "idle" && this.pasteQueueDepth === 0;
|
|
7875
|
-
const accepts = (pane) =>
|
|
7876
|
-
&& this.backend?.isPeriodicRedrawIdlePane?.(pane) === true
|
|
7877
|
-
&& codexLiveRowMatches(pane, nudge.pattern) && !this.backend?.backgroundWorkRunning?.(pane);
|
|
8039
|
+
const accepts = (pane) => this.capacityNudgePaneCurrent(nudge, pane);
|
|
7878
8040
|
const revalidate = () => this.paneWriteLock.run(async () => {
|
|
7879
8041
|
if (!current())
|
|
7880
8042
|
return false;
|
|
@@ -7896,10 +8058,35 @@ export class Daemon extends EventEmitter {
|
|
|
7896
8058
|
}
|
|
7897
8059
|
/** Drop an armed nudge (the user stopped / paused the instance, or the lifecycle gave up on it). */
|
|
7898
8060
|
cancelCapacityNudge(reason) {
|
|
7899
|
-
|
|
8061
|
+
const nudge = this.capacityNudge;
|
|
8062
|
+
if (!nudge)
|
|
7900
8063
|
return;
|
|
7901
8064
|
this.capacityNudge = null;
|
|
7902
8065
|
this.logger.info({ reason }, "Model-capacity nudge cancelled");
|
|
8066
|
+
this.reportCapacityNudgeCancelled(nudge, reason);
|
|
8067
|
+
}
|
|
8068
|
+
/** Only a dispatched channel promise needs a later cancellation notice. */
|
|
8069
|
+
markCapacityNudgePromised(target) {
|
|
8070
|
+
if (this.capacityNudge)
|
|
8071
|
+
this.capacityNudge.promiseTarget = target;
|
|
8072
|
+
}
|
|
8073
|
+
reportCapacityNudgeCancelled(nudge, reason) {
|
|
8074
|
+
const target = nudge.promiseTarget;
|
|
8075
|
+
if (!target)
|
|
8076
|
+
return;
|
|
8077
|
+
nudge.promiseTarget = null;
|
|
8078
|
+
this.emit("capacity_nudge_cancelled", { name: this.name, reason, target });
|
|
8079
|
+
}
|
|
8080
|
+
/**
|
|
8081
|
+
* A failed turn must still be the last live transcript item, with no new
|
|
8082
|
+
* error occurrence, draft, queue, busy status or background work. Codex's
|
|
8083
|
+
* own periodic-redraw idle classifier recognises its empty composer/footer;
|
|
8084
|
+
* never infer inactivity from byte equality or a cached idle state alone.
|
|
8085
|
+
*/
|
|
8086
|
+
capacityNudgePaneCurrent(nudge, pane) {
|
|
8087
|
+
return this.paneAuthoritativelyIdle(pane) && this.backend?.isPeriodicRedrawIdlePane?.(pane) === true
|
|
8088
|
+
&& !this.backend?.backgroundWorkRunning?.(pane) && codexLiveRowMatches(pane, nudge.pattern)
|
|
8089
|
+
&& Daemon.countOccurrences(nudge.pattern, pane) === Daemon.countOccurrences(nudge.pattern, nudge.pane);
|
|
7903
8090
|
}
|
|
7904
8091
|
/** The first live composer of a spawn fixes what is history (see {@link nudgeBaselines}). */
|
|
7905
8092
|
takeNudgeBaselines(pane, patterns) {
|
|
@@ -7922,8 +8109,8 @@ export class Daemon extends EventEmitter {
|
|
|
7922
8109
|
}
|
|
7923
8110
|
/**
|
|
7924
8111
|
* One monitor poll's look at an armed nudge. It is CANCELLED — never postponed — when anything about the instance
|
|
7925
|
-
* moved: a stop / pause / respawn (spawn generation or monitor epoch), a user cancel (delivery epoch), the pane
|
|
7926
|
-
* longer
|
|
8112
|
+
* moved: a stop / pause / respawn (spawn generation or monitor epoch), a user cancel (delivery epoch), the pane
|
|
8113
|
+
* no longer belonging to the failed turn (new output, a draft, another dialog), the
|
|
7927
8114
|
* agent working, or a message waiting for delivery. Only when it is due and still untouched is it fired — once — and
|
|
7928
8115
|
* the injection re-verifies everything under the pane-write lock.
|
|
7929
8116
|
*/
|
|
@@ -7933,10 +8120,11 @@ export class Daemon extends EventEmitter {
|
|
|
7933
8120
|
return;
|
|
7934
8121
|
const gone = nudge.spawn !== this.spawnGeneration || nudge.fence !== this.launchFenceEpoch ? "the instance was stopped, paused or respawned"
|
|
7935
8122
|
: !this.isDeliveryEpochCurrent(nudge.epoch) ? "the user cancelled"
|
|
7936
|
-
:
|
|
7937
|
-
:
|
|
8123
|
+
: nudge.episode !== this.lastErrorEpisode ? "the instance has work of its own"
|
|
8124
|
+
: this.isPaused || this.healthCheckPaused || this.getProcessStatus() === "stopped" ? "the instance is not running"
|
|
7938
8125
|
: this.instanceState === "working" || this.pasteQueueDepth > 0 ? "the instance has work of its own"
|
|
7939
|
-
:
|
|
8126
|
+
: !this.capacityNudgePaneCurrent(nudge, pane) ? "the failed turn is no longer idle with an empty input"
|
|
8127
|
+
: null;
|
|
7940
8128
|
if (gone) {
|
|
7941
8129
|
this.cancelCapacityNudge(gone);
|
|
7942
8130
|
return;
|
|
@@ -7944,29 +8132,38 @@ export class Daemon extends EventEmitter {
|
|
|
7944
8132
|
if (performance.now() < nudge.dueAt)
|
|
7945
8133
|
return;
|
|
7946
8134
|
this.capacityNudge = null; // fired: once per episode
|
|
7947
|
-
void this.sendCapacityNudge(nudge).catch(err =>
|
|
8135
|
+
void this.sendCapacityNudge(nudge).catch(err => {
|
|
8136
|
+
this.logger.warn({ err: err.message }, "Model-capacity nudge failed");
|
|
8137
|
+
this.reportCapacityNudgeCancelled(nudge, "the retry could not be submitted");
|
|
8138
|
+
});
|
|
7948
8139
|
}
|
|
7949
8140
|
async sendCapacityNudge(nudge) {
|
|
7950
8141
|
// Everything the nudge was armed under, asked again after EVERY await that precedes a write — inside the paste
|
|
7951
8142
|
// primitive too (its capture, its paste, its pause, its Enter, its retry): once any of it is false nothing more is
|
|
7952
8143
|
// written. A message queued behind the lock, or the agent starting a turn, is "work of its own" like at the poll.
|
|
7953
8144
|
const current = () => nudge.spawn === this.spawnGeneration && nudge.fence === this.launchFenceEpoch
|
|
8145
|
+
&& nudge.episode === this.lastErrorEpisode
|
|
7954
8146
|
&& this.isDeliveryEpochCurrent(nudge.epoch) && !this.isPaused && !this.healthCheckPaused
|
|
7955
8147
|
&& !this.startupAborted && this.getProcessStatus() !== "stopped"
|
|
7956
8148
|
&& this.pasteQueueDepth === 0 && this.instanceState !== "working";
|
|
7957
8149
|
// Exclusive with every delivery: nothing can paste a turn between the verdict below and the nudge.
|
|
7958
|
-
|
|
7959
|
-
|
|
7960
|
-
|
|
7961
|
-
|
|
7962
|
-
|
|
7963
|
-
|
|
7964
|
-
|
|
7965
|
-
|
|
7966
|
-
|
|
8150
|
+
let sent = false;
|
|
8151
|
+
try {
|
|
8152
|
+
await this.paneWriteLock.run(async () => {
|
|
8153
|
+
if (!this.tmux)
|
|
8154
|
+
return;
|
|
8155
|
+
// ONE capture decides failed-turn ownership and empty/idle input.
|
|
8156
|
+
sent = await this.submitSystemPaste(t("inst.codex_capacity_nudge_text"), "capacity-continue", {
|
|
8157
|
+
current,
|
|
8158
|
+
accept: pane => this.capacityNudgePaneCurrent(nudge, pane),
|
|
8159
|
+
});
|
|
8160
|
+
this.logger.info({ sent }, "Model-capacity nudge attempted");
|
|
7967
8161
|
});
|
|
7968
|
-
|
|
7969
|
-
|
|
8162
|
+
}
|
|
8163
|
+
finally {
|
|
8164
|
+
if (!sent)
|
|
8165
|
+
this.reportCapacityNudgeCancelled(nudge, "the retry could not be submitted");
|
|
8166
|
+
}
|
|
7970
8167
|
}
|
|
7971
8168
|
/** What identifies the request on screen: the backend's own identity when it has one, else the whole screen verbatim. */
|
|
7972
8169
|
static screenOf(dialog, pane) {
|
|
@@ -8026,6 +8223,73 @@ export class Daemon extends EventEmitter {
|
|
|
8026
8223
|
this.logger.warn({ dialog: dialog.description, attempts: episode.ignored, holdsDeliveries }, "Runtime dialog is ignoring AgEnD's answer — reporting for a human");
|
|
8027
8224
|
this.emit("dialog_answer_ignored", { name: this.name, description: dialog.description, attempts: episode.ignored, holdsDeliveries });
|
|
8028
8225
|
}
|
|
8226
|
+
/**
|
|
8227
|
+
* #2675: a held card a person can answer from the chat (the dialog's remoteCard reader) is posted once it has stayed
|
|
8228
|
+
* the same card for REMOTE_CARD_STABLE_MS — `remote_card` with what it asks; the fleet posts it with its buttons. When
|
|
8229
|
+
* it closes or another card replaces it, `remote_card_gone` retires that post. Moving through the card (ours or the
|
|
8230
|
+
* person's in the pane) is the same card: its identity has no cursor. `readAt` is when the pane's capture was started
|
|
8231
|
+
* (performance.now): a read that started before the newest one used here finishes late and says nothing about now —
|
|
8232
|
+
* it may neither retire a newer card nor bring an older one back (#2677 review).
|
|
8233
|
+
*/
|
|
8234
|
+
trackRemoteCard(dialog, pane, readAt) {
|
|
8235
|
+
if (readAt < this.remoteCardReadAt)
|
|
8236
|
+
return;
|
|
8237
|
+
this.remoteCardReadAt = readAt;
|
|
8238
|
+
const card = dialog?.remoteCard?.(pane) ?? null;
|
|
8239
|
+
const shown = this.remoteCardShown;
|
|
8240
|
+
if (shown && shown.identity !== card?.identity) {
|
|
8241
|
+
this.remoteCardShown = null;
|
|
8242
|
+
if (shown.posted)
|
|
8243
|
+
this.emit("remote_card_gone", { name: this.name, identity: shown.identity });
|
|
8244
|
+
}
|
|
8245
|
+
if (!card)
|
|
8246
|
+
return;
|
|
8247
|
+
if (!this.remoteCardShown) {
|
|
8248
|
+
this.remoteCardShown = { identity: card.identity, since: performance.now(), posted: false };
|
|
8249
|
+
return;
|
|
8250
|
+
}
|
|
8251
|
+
if (this.remoteCardShown.posted || performance.now() - this.remoteCardShown.since < REMOTE_CARD_STABLE_MS)
|
|
8252
|
+
return;
|
|
8253
|
+
this.remoteCardShown.posted = true;
|
|
8254
|
+
this.logger.info({ kind: card.kind }, "A card a person can answer from the chat is on screen — posting it");
|
|
8255
|
+
this.emit("remote_card", { name: this.name, card });
|
|
8256
|
+
}
|
|
8257
|
+
/**
|
|
8258
|
+
* #2675: answer the card on screen from the chat, as the person chose: only while it is the card they saw
|
|
8259
|
+
* (`identity`), on a verified cursor, under the pane write lock (remote-card-answer.ts). Never logs the answer.
|
|
8260
|
+
*/
|
|
8261
|
+
async answerRemoteCard(identity, action) {
|
|
8262
|
+
const tmux = this.tmux;
|
|
8263
|
+
const readers = this.runtimeDialogs().filter(dialog => dialog.remoteCard);
|
|
8264
|
+
if (!tmux || readers.length === 0)
|
|
8265
|
+
return { outcome: "stale" };
|
|
8266
|
+
if (this.remoteCardAnswering)
|
|
8267
|
+
return { outcome: "refused", reason: "another answer to this card is being keyed in" };
|
|
8268
|
+
this.remoteCardAnswering = true;
|
|
8269
|
+
try {
|
|
8270
|
+
const outcome = await answerRemoteCard({
|
|
8271
|
+
locked: fn => this.paneWriteLock.run(fn),
|
|
8272
|
+
// An unreadable screen (a failed capture, another spawn's pane) is unknown — never "the card closed".
|
|
8273
|
+
read: () => this.tmux === tmux ? tmux.capturePane().catch(() => null) : Promise.resolve(null),
|
|
8274
|
+
key: key => this.tmux !== tmux ? Promise.resolve(false) : key === "Space" ? tmux.sendKeys(" ") : tmux.sendSpecialKey(key),
|
|
8275
|
+
paste: text => this.tmux !== tmux ? Promise.resolve(false) : tmux.pasteBuffer(text),
|
|
8276
|
+
sleep: ms => new Promise(resolve => setTimeout(resolve, ms)),
|
|
8277
|
+
now: () => performance.now(),
|
|
8278
|
+
}, pane => {
|
|
8279
|
+
for (const dialog of readers) {
|
|
8280
|
+
const card = dialog.remoteCard(pane);
|
|
8281
|
+
if (card)
|
|
8282
|
+
return card;
|
|
8283
|
+
}
|
|
8284
|
+
return null;
|
|
8285
|
+
}, identity, action);
|
|
8286
|
+
this.logger.info({ action: action.kind, ...outcome }, "Answered a card from the chat");
|
|
8287
|
+
return outcome;
|
|
8288
|
+
}
|
|
8289
|
+
finally {
|
|
8290
|
+
this.remoteCardAnswering = false;
|
|
8291
|
+
}
|
|
8292
|
+
}
|
|
8029
8293
|
/**
|
|
8030
8294
|
* Remember how long a blocking dialog has been on screen. Once it has
|
|
8031
8295
|
* outlived DIALOG_PARKED_NOTIFY_MS — the auto-dismiss did not take, or the
|
|
@@ -10233,7 +10497,9 @@ export class Daemon extends EventEmitter {
|
|
|
10233
10497
|
// false and must not clear the window either.
|
|
10234
10498
|
if (replyAttempt?.completionAction) {
|
|
10235
10499
|
const snap = this.turnReplyGuard.snapshot();
|
|
10236
|
-
|
|
10500
|
+
// generation 0 is a pre-arm token applied to the live generation (#2544).
|
|
10501
|
+
if (snap && (snap.generation === replyAttempt.generation || replyAttempt.generation === 0)
|
|
10502
|
+
&& snap.phase === "awaiting"
|
|
10237
10503
|
&& snap.completionDelivered && !snap.cancelledByUser) {
|
|
10238
10504
|
this.clearReplyGuardConfirm();
|
|
10239
10505
|
}
|
|
@@ -10654,7 +10920,7 @@ export class Daemon extends EventEmitter {
|
|
|
10654
10920
|
fleetCwd: process.cwd(),
|
|
10655
10921
|
onWarning: (warning) => {
|
|
10656
10922
|
if (warning.problem === "fleet_dir_fallback") {
|
|
10657
|
-
this.logger.warn({ field: warning.field, path: warning.path, legacyPath: warning.legacyPath }, `${warning.field} file: ref is not under the instance's working directory (${warning.path}); read ${warning.legacyPath} from the fleet directory instead — make the path absolute or move the file.
|
|
10923
|
+
this.logger.warn({ field: warning.field, path: warning.path, legacyPath: warning.legacyPath }, `${warning.field} file: ref is not under the instance's working directory (${warning.path}); read ${warning.legacyPath} from the fleet directory instead — make the path absolute or move the file. A later release will remove this fallback.`);
|
|
10658
10924
|
const key = `${warning.field}:${warning.path}`;
|
|
10659
10925
|
if (!this.promptFileFallbackNoticed.has(key)) {
|
|
10660
10926
|
this.promptFileFallbackNoticed.add(key);
|
|
@@ -10742,7 +11008,7 @@ export class Daemon extends EventEmitter {
|
|
|
10742
11008
|
mcpEnv.AGEND_TOOL_SET = toolSet;
|
|
10743
11009
|
// The dashboard features the person switched off: out of the tool menu from this start (the fleet refuses them
|
|
10744
11010
|
// at once, whatever the menu still shows).
|
|
10745
|
-
const dashboardOff = dashboardFeaturesOff(this.config
|
|
11011
|
+
const dashboardOff = dashboardFeaturesOff(dashboardAccessOf(this.config));
|
|
10746
11012
|
if (dashboardOff.length)
|
|
10747
11013
|
mcpEnv.AGEND_DASHBOARD_OFF = dashboardOff.join(",");
|
|
10748
11014
|
if (this.config.display_name)
|
|
@@ -10883,9 +11149,14 @@ export class Daemon extends EventEmitter {
|
|
|
10883
11149
|
beginSpawn() {
|
|
10884
11150
|
this.unclearOomStopped = false;
|
|
10885
11151
|
this.publishWorkingState("before_spawn");
|
|
11152
|
+
this.recoveryContinuationId = null;
|
|
10886
11153
|
this.workingBackgroundObservation = null;
|
|
11154
|
+
this.ownedWorkingSession = null;
|
|
11155
|
+
this.recoveryBootSettled = false;
|
|
11156
|
+
this.recoveryNativeWork = false;
|
|
11157
|
+
this.recoveryCancelled = false;
|
|
10887
11158
|
this.dialogAnswers = null;
|
|
10888
|
-
this.
|
|
11159
|
+
this.cancelCapacityNudge("the instance was stopped, paused or respawned");
|
|
10889
11160
|
this.lastErrorEpisode = null;
|
|
10890
11161
|
this.nudgeBaselines = null;
|
|
10891
11162
|
// A relaunch settles any outstanding quit watch: the CLI came back.
|
|
@@ -10916,6 +11187,8 @@ export class Daemon extends EventEmitter {
|
|
|
10916
11187
|
this.spawnDepth = Math.max(0, this.spawnDepth - 1);
|
|
10917
11188
|
if (this.spawnDepth > 0)
|
|
10918
11189
|
return; // an enclosing spawn is still running
|
|
11190
|
+
if (!this.recoveryBootWorkPending)
|
|
11191
|
+
this.recoveryBootSettled = true;
|
|
10919
11192
|
this.spawning = false;
|
|
10920
11193
|
const resolve = this.resolveSpawnSettled;
|
|
10921
11194
|
this.spawnSettled = null;
|
|
@@ -11174,7 +11447,7 @@ export class Daemon extends EventEmitter {
|
|
|
11174
11447
|
// out loud — this start does NOT continue the previous conversation.
|
|
11175
11448
|
this.logger.error({ attempts: failures }, "Giving up on resuming after repeated startup failures — starting fresh. "
|
|
11176
11449
|
+ "The previous conversation is NOT continued; its context is lost to this session.");
|
|
11177
|
-
this.emit("context_lost", this.name, "resume_repeatedly_failed");
|
|
11450
|
+
this.emit("context_lost", this.name, "resume_repeatedly_failed", failures);
|
|
11178
11451
|
}
|
|
11179
11452
|
this.setSessionAside(proven ? "cli_reported_no_conversation" : "resume_failed_repeatedly");
|
|
11180
11453
|
this.skipResume = true;
|
|
@@ -12369,6 +12642,13 @@ export class Daemon extends EventEmitter {
|
|
|
12369
12642
|
const sid = this.backend?.getSessionId();
|
|
12370
12643
|
if (!sid)
|
|
12371
12644
|
return;
|
|
12645
|
+
if ((this.config.backend ?? "claude-code") === "claude-code" && Buffer.byteLength(sid) <= 256
|
|
12646
|
+
&& !/[\u0000-\u001f\u007f]/.test(sid) && this.ownedWorkingSession !== sid) {
|
|
12647
|
+
this.ownedWorkingSession = sid;
|
|
12648
|
+
// Reuse this existing owned checkpoint. An idle instance may have no
|
|
12649
|
+
// later state edge before an OOM; its journal must not stay unknown.
|
|
12650
|
+
this.publishWorkingState("process");
|
|
12651
|
+
}
|
|
12372
12652
|
const path = join(this.instanceDir, "session-id");
|
|
12373
12653
|
// Idle observations happen after turns. Avoid needless writes (and mtime
|
|
12374
12654
|
// churn) while still updating the marker when /new changes the session.
|