@gajae-code/coding-agent 0.11.7 → 0.11.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +73 -1
- package/dist/types/cli/args.d.ts +0 -2
- package/dist/types/cli.d.ts +1 -0
- package/dist/types/config/keybindings.d.ts +33 -3
- package/dist/types/config/model-profile-activation.d.ts +4 -3
- package/dist/types/config/model-profile-contract.d.ts +38 -0
- package/dist/types/config/model-profiles.d.ts +2 -3
- package/dist/types/config/settings-schema.d.ts +15 -2
- package/dist/types/edit/modes/patch.d.ts +13 -0
- package/dist/types/edit/path-mutation-lock.d.ts +15 -0
- package/dist/types/eval/py/executor.d.ts +1 -1
- package/dist/types/eval/py/tool-bridge.d.ts +1 -2
- package/dist/types/gjc-runtime/deep-interview-ambiguity.d.ts +12 -5
- package/dist/types/gjc-runtime/deep-interview-draft.d.ts +8 -0
- package/dist/types/gjc-runtime/deep-interview-payload.d.ts +41 -0
- package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +32 -17
- package/dist/types/gjc-runtime/deep-interview-repair.d.ts +17 -0
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +135 -4
- package/dist/types/gjc-runtime/repository-binding.d.ts +63 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +44 -0
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +1 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +24 -0
- package/dist/types/gjc-runtime/workflow-manifest.d.ts +5 -3
- package/dist/types/main.d.ts +8 -0
- package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +2 -1
- package/dist/types/modes/components/tool-status-header.d.ts +3 -2
- package/dist/types/modes/components/welcome.d.ts +4 -0
- package/dist/types/modes/controllers/command-controller.d.ts +2 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
- package/dist/types/modes/interactive-mode.d.ts +5 -2
- package/dist/types/modes/prompt-action-autocomplete.d.ts +6 -1
- package/dist/types/modes/prompt-suggestion-controller.d.ts +35 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +2 -2
- package/dist/types/modes/types.d.ts +3 -0
- package/dist/types/modes/utils/hotkeys-markdown.d.ts +2 -1
- package/dist/types/runtime-mcp/manager.d.ts +1 -1
- package/dist/types/runtime-mcp/oauth-flow.d.ts +0 -5
- package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +14 -0
- package/dist/types/runtime-mcp/redaction.d.ts +2 -0
- package/dist/types/sdk/broker/broker.d.ts +2 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +6 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +7 -1
- package/dist/types/sdk/bus/config-commands.d.ts +2 -2
- package/dist/types/sdk/bus/config.d.ts +2 -0
- package/dist/types/sdk/bus/conversation-store.d.ts +1 -0
- package/dist/types/sdk/bus/index.d.ts +25 -0
- package/dist/types/sdk/bus/notification-orchestration.d.ts +1 -0
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +105 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +15 -3
- package/dist/types/sdk/bus/telegram-daemon-control.d.ts +5 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +128 -1
- package/dist/types/sdk/bus/telegram-reference.d.ts +2 -0
- package/dist/types/sdk/host/control/operations.d.ts +1 -1
- package/dist/types/sdk/host/host.d.ts +1 -1
- package/dist/types/sdk/host/query/handlers.d.ts +9 -0
- package/dist/types/sdk/index.d.ts +2 -0
- package/dist/types/sdk/prompt-status.d.ts +88 -0
- package/dist/types/sdk/session-directory.d.ts +1 -1
- package/dist/types/sdk/startup-capability.d.ts +3 -0
- package/dist/types/session/agent-session.d.ts +2 -0
- package/dist/types/session/internal/managed-session-scope.d.ts +22 -3
- package/dist/types/session/internal/managed-session-storage.d.ts +24 -4
- package/dist/types/session/internal/native-publish-outcome.d.ts +48 -0
- package/dist/types/session/session-manager.d.ts +5 -1
- package/dist/types/session/session-storage.d.ts +31 -0
- package/dist/types/skill-state/active-state.d.ts +4 -0
- package/dist/types/task/index.d.ts +3 -1
- package/dist/types/task/receipt.d.ts +2 -0
- package/dist/types/task/types.d.ts +94 -0
- package/dist/types/tools/ask.d.ts +454 -174
- package/dist/types/tools/image-gen.d.ts +47 -1
- package/dist/types/tools/index.d.ts +4 -0
- package/dist/types/utils/prompt-suggestion.d.ts +33 -0
- package/package.json +8 -8
- package/scripts/capture-platform-shortcut-labels-showcase.ts +342 -0
- package/scripts/dogfood-repository-binding.ts +209 -0
- package/scripts/generate-sdk-operation-inventory.ts +2 -0
- package/scripts/run-test-manifest.ts +4 -4
- package/src/cli/args.ts +2 -92
- package/src/cli/mcp-cli.ts +4 -21
- package/src/cli/notify-cli.ts +5 -38
- package/src/cli.ts +50 -0
- package/src/commands/deep-interview.ts +9 -4
- package/src/commands/sdk.ts +2 -2
- package/src/commit/agentic/index.ts +3 -3
- package/src/commit/map-reduce/index.ts +2 -2
- package/src/config/keybindings.ts +157 -24
- package/src/config/model-profile-activation.ts +7 -18
- package/src/config/model-profile-contract.ts +161 -0
- package/src/config/model-profiles.ts +2 -3
- package/src/config/model-registry.ts +11 -1
- package/src/config/settings-schema.ts +17 -2
- package/src/coordinator-mcp/model-preset.ts +21 -74
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +80 -165
- package/src/defaults/gjc/skills/ralplan/SKILL.md +3 -1
- package/src/edit/index.ts +3 -6
- package/src/edit/modes/patch.ts +38 -1
- package/src/edit/modes/replace.ts +42 -0
- package/src/edit/path-mutation-lock.ts +112 -0
- package/src/eval/py/executor.ts +30 -11
- package/src/eval/py/prelude.py +2 -2
- package/src/eval/py/tool-bridge.ts +34 -18
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +2 -1
- package/src/gjc-runtime/deep-interview-ambiguity.ts +68 -47
- package/src/gjc-runtime/deep-interview-draft.ts +842 -0
- package/src/gjc-runtime/deep-interview-payload.ts +262 -0
- package/src/gjc-runtime/deep-interview-recorder.ts +294 -226
- package/src/gjc-runtime/deep-interview-repair.ts +1674 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +264 -103
- package/src/gjc-runtime/deep-interview-state.ts +916 -4
- package/src/gjc-runtime/launch-tmux.ts +1 -1
- package/src/gjc-runtime/managed-owner-supervisor.ts +83 -4
- package/src/gjc-runtime/ralplan-runtime.ts +78 -4
- package/src/gjc-runtime/repository-binding.ts +267 -0
- package/src/gjc-runtime/session-state-sidecar.ts +25 -9
- package/src/gjc-runtime/state-writer.ts +166 -2
- package/src/gjc-runtime/tmux-owner-isolation.ts +2 -1
- package/src/gjc-runtime/tmux-sessions.ts +45 -6
- package/src/gjc-runtime/ultragoal-guard.ts +5 -5
- package/src/gjc-runtime/ultragoal-runtime.ts +181 -19
- package/src/gjc-runtime/workflow-manifest.generated.json +386 -1
- package/src/gjc-runtime/workflow-manifest.ts +191 -5
- package/src/internal-urls/docs-index.generated.ts +8 -6
- package/src/internal-urls/local-protocol.ts +33 -16
- package/src/internal-urls/mcp-protocol.ts +73 -11
- package/src/main.ts +25 -10
- package/src/modes/DESIGN.md +5 -0
- package/src/modes/acp/acp-agent.ts +50 -16
- package/src/modes/components/model-selector.ts +42 -8
- package/src/modes/components/plan-preview-overlay.ts +1 -1
- package/src/modes/components/runtime-mcp-add-wizard.ts +5 -0
- package/src/modes/components/tool-status-header.ts +20 -4
- package/src/modes/components/welcome.ts +32 -20
- package/src/modes/controllers/command-controller.ts +97 -54
- package/src/modes/controllers/event-controller.ts +10 -0
- package/src/modes/controllers/input-controller.ts +7 -2
- package/src/modes/controllers/runtime-mcp-command-controller.ts +29 -6
- package/src/modes/controllers/selector-controller.ts +46 -54
- package/src/modes/interactive-mode.ts +70 -36
- package/src/modes/prompt-action-autocomplete.ts +32 -13
- package/src/modes/prompt-suggestion-controller.ts +96 -0
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +2 -2
- package/src/modes/types.ts +3 -0
- package/src/modes/utils/hotkeys-markdown.ts +64 -36
- package/src/modes/utils/ui-helpers.ts +5 -3
- package/src/prompts/agents/architect.md +0 -1
- package/src/prompts/agents/critic.md +0 -1
- package/src/prompts/system/prompt-suggestion-system.md +32 -0
- package/src/prompts/system/system-prompt.md +7 -4
- package/src/prompts/tools/cron.md +3 -1
- package/src/runtime-mcp/json-rpc.ts +7 -4
- package/src/runtime-mcp/manager.ts +51 -53
- package/src/runtime-mcp/oauth-flow.ts +0 -72
- package/src/runtime-mcp/plugin-network-boundary.ts +101 -0
- package/src/runtime-mcp/redaction.ts +30 -0
- package/src/runtime-mcp/transports/http.ts +13 -6
- package/src/sdk/broker/broker.ts +34 -20
- package/src/sdk/broker/lifecycle.ts +185 -36
- package/src/sdk/broker/session-index.ts +11 -1
- package/src/sdk/bus/chat-daemon-control.ts +20 -5
- package/src/sdk/bus/config-commands.ts +2 -2
- package/src/sdk/bus/config.ts +9 -1
- package/src/sdk/bus/conversation-store.ts +40 -6
- package/src/sdk/bus/index.ts +240 -65
- package/src/sdk/bus/lifecycle-commands.ts +47 -3
- package/src/sdk/bus/lifecycle-control-runtime.ts +11 -2
- package/src/sdk/bus/lifecycle-orchestrator.ts +32 -1
- package/src/sdk/bus/notification-orchestration.ts +12 -0
- package/src/sdk/bus/notification-service.ts +27 -2
- package/src/sdk/bus/prompt-reconciliation.ts +239 -0
- package/src/sdk/bus/recent-activity.ts +90 -26
- package/src/sdk/bus/telegram-daemon-contract.ts +15 -3
- package/src/sdk/bus/telegram-daemon-control.ts +145 -33
- package/src/sdk/bus/telegram-daemon.ts +853 -112
- package/src/sdk/bus/telegram-reference.ts +20 -2
- package/src/sdk/cli/session-cli.ts +15 -2
- package/src/sdk/host/control/dispatch.ts +1 -1
- package/src/sdk/host/control/operations.ts +1 -1
- package/src/sdk/host/host.ts +2 -6
- package/src/sdk/host/query/handlers.ts +70 -5
- package/src/sdk/index.ts +12 -0
- package/src/sdk/prompt-status.ts +97 -0
- package/src/sdk/protocol/operation-inventory.generated.json +47 -0
- package/src/sdk/protocol/operation-registry.ts +18 -4
- package/src/sdk/session-directory.ts +5 -1
- package/src/sdk/session.ts +2 -0
- package/src/sdk/startup-capability.ts +9 -1
- package/src/session/agent-session.ts +138 -5
- package/src/session/blob-store.ts +7 -2
- package/src/session/internal/managed-session-scope.ts +998 -133
- package/src/session/internal/managed-session-storage.ts +268 -203
- package/src/session/internal/native-publish-outcome.ts +266 -0
- package/src/session/session-manager.ts +406 -360
- package/src/session/session-storage.ts +151 -11
- package/src/skill-state/active-state.ts +10 -0
- package/src/task/executor.ts +13 -5
- package/src/task/gjc-command.ts +2 -2
- package/src/task/index.ts +121 -15
- package/src/task/receipt.ts +3 -0
- package/src/task/types.ts +34 -3
- package/src/tools/ask.ts +199 -88
- package/src/tools/ast-edit.ts +2 -2
- package/src/tools/bash-pty-selection.ts +2 -2
- package/src/tools/browser/screenshot-format.ts +6 -1
- package/src/tools/cron.ts +1 -1
- package/src/tools/image-gen.ts +178 -6
- package/src/tools/index.ts +4 -0
- package/src/tools/render-utils.ts +7 -1
- package/src/utils/edit-mode.ts +2 -2
- package/src/utils/prompt-suggestion.ts +265 -0
- package/src/utils/sixel.ts +3 -3
- package/src/web/insane/url-guard.ts +3 -1
- package/src/web/scrapers/docs-rs.ts +30 -5
|
@@ -25,9 +25,11 @@ import type {
|
|
|
25
25
|
import { OWNERSHIP_MISMATCH_MESSAGE, ownershipMismatchRecovery } from "../../daemon/operator-contract";
|
|
26
26
|
import { resolveGjcRuntimeSpawnInfo } from "../../daemon/runtime";
|
|
27
27
|
import { isProcessIncarnation } from "../broker/process-incarnation";
|
|
28
|
+
|
|
28
29
|
import { getNotificationConfig, isTelegramConfigured, tokenFingerprint } from "./config";
|
|
29
30
|
import { exactUnlinkNotificationFile, readNotificationEndpointFile } from "./notification-service";
|
|
30
31
|
import {
|
|
32
|
+
type AttestedLegacyDaemonOwner,
|
|
31
33
|
confirmTelegramDaemonSpawn,
|
|
32
34
|
type DaemonState,
|
|
33
35
|
daemonPaths,
|
|
@@ -35,6 +37,7 @@ import {
|
|
|
35
37
|
isCurrentCompatibleOwner,
|
|
36
38
|
isFreshLiveOwner,
|
|
37
39
|
isSignalableMatchingOwner,
|
|
40
|
+
readAttestedLegacyDaemonOwner,
|
|
38
41
|
readDaemonRoots,
|
|
39
42
|
readDaemonState,
|
|
40
43
|
spawnTelegramDaemonOwner,
|
|
@@ -109,16 +112,19 @@ export async function clearTelegramControlRequest(
|
|
|
109
112
|
|
|
110
113
|
export interface DaemonProcessReference {
|
|
111
114
|
incarnation: string;
|
|
115
|
+
/** Whether the platform delivers the requested signal cooperatively or as a hard termination. */
|
|
116
|
+
termination: "cooperative" | "hard";
|
|
112
117
|
signalRoot(signal: NodeJS.Signals): void;
|
|
113
118
|
}
|
|
114
119
|
|
|
115
|
-
function defaultProcessReference(pid: number, platform = os.platform()): DaemonProcessReference | undefined {
|
|
120
|
+
export function defaultProcessReference(pid: number, platform = os.platform()): DaemonProcessReference | undefined {
|
|
116
121
|
try {
|
|
117
122
|
const processRef = Process.fromPid(pid);
|
|
118
123
|
if (!processRef || !isProcessIncarnation(processRef.incarnation)) return undefined;
|
|
119
124
|
const incarnation = processRef.incarnation;
|
|
120
125
|
return {
|
|
121
126
|
incarnation,
|
|
127
|
+
termination: platform === "win32" ? "hard" : "cooperative",
|
|
122
128
|
signalRoot: signal => {
|
|
123
129
|
const nativeSignal = os.constants.signals[signal];
|
|
124
130
|
if (nativeSignal === undefined) throw new Error(`Unsupported signal: ${signal}`);
|
|
@@ -190,7 +196,6 @@ export class TelegramDaemonController implements BuiltInDaemonController {
|
|
|
190
196
|
private readonly now: () => number;
|
|
191
197
|
private readonly processReference: (pid: number) => DaemonProcessReference | undefined;
|
|
192
198
|
private readonly waitStepMs: number;
|
|
193
|
-
|
|
194
199
|
constructor(
|
|
195
200
|
private readonly settings: Settings,
|
|
196
201
|
private readonly deps: TelegramDaemonControlDeps = {},
|
|
@@ -315,31 +320,28 @@ export class TelegramDaemonController implements BuiltInDaemonController {
|
|
|
315
320
|
tokenFingerprint: string,
|
|
316
321
|
chatId: string,
|
|
317
322
|
signal: NodeJS.Signals,
|
|
318
|
-
): Promise<"signaled" | "already_gone" | "ownership_changed"> {
|
|
323
|
+
): Promise<"signaled" | "already_gone" | "ownership_changed" | "hard_termination"> {
|
|
319
324
|
const current = await readDaemonState(this.settings, this.fsImpl);
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
current.
|
|
326
|
-
current.
|
|
327
|
-
current.
|
|
328
|
-
current.
|
|
329
|
-
current.
|
|
330
|
-
current.tokenFingerprint
|
|
331
|
-
current.chatId
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
// A matching captured owner that exited between the request and recheck has
|
|
337
|
-
// completed the handoff. A live owner with changed or unavailable provenance
|
|
338
|
-
// remains ambiguous and must not be treated as stopped.
|
|
325
|
+
|
|
326
|
+
const modernCurrentMatches =
|
|
327
|
+
hasSafeDaemonStateShape(current) &&
|
|
328
|
+
current.ownerId === captured.ownerId &&
|
|
329
|
+
current.acquisitionId === captured.acquisitionId &&
|
|
330
|
+
current.pid === captured.pid &&
|
|
331
|
+
current.generation === captured.generation &&
|
|
332
|
+
current.incarnation === captured.incarnation &&
|
|
333
|
+
current.tokenFingerprint === captured.tokenFingerprint &&
|
|
334
|
+
current.chatId === captured.chatId &&
|
|
335
|
+
current.tokenFingerprint === tokenFingerprint &&
|
|
336
|
+
current.chatId === chatId;
|
|
337
|
+
// The signal is privileged: the persisted record must still be the exact
|
|
338
|
+
// modern owner. Legacy generation-3 records remain migration evidence only;
|
|
339
|
+
// this controller never upgrades or signals them.
|
|
340
|
+
if (!modernCurrentMatches) return "ownership_changed";
|
|
339
341
|
if (!this.pidAlive(captured.pid)) return "already_gone";
|
|
340
342
|
if (
|
|
341
343
|
!isSignalableMatchingOwner({
|
|
342
|
-
state: current,
|
|
344
|
+
state: modernCurrentMatches ? current : captured,
|
|
343
345
|
tokenFingerprint,
|
|
344
346
|
chatId,
|
|
345
347
|
pidAlive: this.pidAlive,
|
|
@@ -352,6 +354,69 @@ export class TelegramDaemonController implements BuiltInDaemonController {
|
|
|
352
354
|
// the exit-and-reuse window between ordinary provenance checks and signaling.
|
|
353
355
|
// Its identity must still be the exact persisted incarnation before use.
|
|
354
356
|
if (!processRef || processRef.incarnation !== captured.incarnation) return "ownership_changed";
|
|
357
|
+
if (signal === "SIGTERM" && processRef.termination === "hard") return "hard_termination";
|
|
358
|
+
try {
|
|
359
|
+
processRef.signalRoot(signal);
|
|
360
|
+
} catch {
|
|
361
|
+
return "already_gone";
|
|
362
|
+
}
|
|
363
|
+
return "signaled";
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
#currentConfigurationMatches(tokenFingerprintValue: string, chatId: string): boolean {
|
|
367
|
+
const current = getNotificationConfig(this.settings);
|
|
368
|
+
return (
|
|
369
|
+
isTelegramConfigured(current) &&
|
|
370
|
+
tokenFingerprint(current.botToken) === tokenFingerprintValue &&
|
|
371
|
+
current.chatId === chatId
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
async #signalAttestedLegacyOwner(
|
|
376
|
+
captured: AttestedLegacyDaemonOwner,
|
|
377
|
+
tokenFingerprint: string,
|
|
378
|
+
chatId: string,
|
|
379
|
+
signal: NodeJS.Signals,
|
|
380
|
+
): Promise<"signaled" | "already_gone" | "ownership_changed" | "hard_termination"> {
|
|
381
|
+
if (!this.#currentConfigurationMatches(tokenFingerprint, chatId)) return "ownership_changed";
|
|
382
|
+
const current = await readAttestedLegacyDaemonOwner({
|
|
383
|
+
settings: this.settings,
|
|
384
|
+
fs: this.fsImpl,
|
|
385
|
+
now: this.now,
|
|
386
|
+
pidIncarnation: this.deps.pidIncarnation,
|
|
387
|
+
tokenFingerprint,
|
|
388
|
+
chatId,
|
|
389
|
+
});
|
|
390
|
+
if (
|
|
391
|
+
!current ||
|
|
392
|
+
current.state.pid !== captured.state.pid ||
|
|
393
|
+
current.state.ownerId !== captured.state.ownerId ||
|
|
394
|
+
current.state.startedAt !== captured.state.startedAt ||
|
|
395
|
+
current.incarnation !== captured.incarnation
|
|
396
|
+
)
|
|
397
|
+
return "ownership_changed";
|
|
398
|
+
if (!this.pidAlive(captured.state.pid)) return "already_gone";
|
|
399
|
+
const processRef = this.processReference(captured.state.pid);
|
|
400
|
+
if (!processRef || processRef.incarnation !== captured.incarnation) return "ownership_changed";
|
|
401
|
+
const rechecked = await readAttestedLegacyDaemonOwner({
|
|
402
|
+
settings: this.settings,
|
|
403
|
+
fs: this.fsImpl,
|
|
404
|
+
now: this.now,
|
|
405
|
+
pidIncarnation: this.deps.pidIncarnation,
|
|
406
|
+
tokenFingerprint,
|
|
407
|
+
chatId,
|
|
408
|
+
});
|
|
409
|
+
if (
|
|
410
|
+
!rechecked ||
|
|
411
|
+
rechecked.state.pid !== current.state.pid ||
|
|
412
|
+
rechecked.state.ownerId !== current.state.ownerId ||
|
|
413
|
+
rechecked.state.startedAt !== current.state.startedAt ||
|
|
414
|
+
rechecked.incarnation !== current.incarnation ||
|
|
415
|
+
!this.#currentConfigurationMatches(tokenFingerprint, chatId) ||
|
|
416
|
+
!this.pidAlive(captured.state.pid)
|
|
417
|
+
)
|
|
418
|
+
return "ownership_changed";
|
|
419
|
+
if (signal === "SIGTERM" && processRef.termination === "hard") return "hard_termination";
|
|
355
420
|
try {
|
|
356
421
|
processRef.signalRoot(signal);
|
|
357
422
|
} catch {
|
|
@@ -376,7 +441,10 @@ export class TelegramDaemonController implements BuiltInDaemonController {
|
|
|
376
441
|
return this.stopOrReload("reload", opts);
|
|
377
442
|
}
|
|
378
443
|
|
|
379
|
-
async reloadForGenerationUpgrade(
|
|
444
|
+
async reloadForGenerationUpgrade(
|
|
445
|
+
opts: DaemonOperationOptions = {},
|
|
446
|
+
attestedLegacyUpgrade = false,
|
|
447
|
+
): Promise<TelegramGenerationReloadResult> {
|
|
380
448
|
// A generation upgrade MUST replace an incompatible older-generation owner to
|
|
381
449
|
// avoid a permanent single-poller deadlock. Unlike a manual `gjc daemon
|
|
382
450
|
// reload`, this automatic path force-escalates to SIGKILL when the old owner
|
|
@@ -384,7 +452,7 @@ export class TelegramDaemonController implements BuiltInDaemonController {
|
|
|
384
452
|
// self-recovers instead of failing closed and asking the operator to rerun
|
|
385
453
|
// with --force. The SIGKILL remains fenced to the still-live, still-matching
|
|
386
454
|
// captured owner (same ownerId + pid), so a fresh replacement is never killed.
|
|
387
|
-
const operation = await this.stopOrReload("reload", { ...opts, force: true });
|
|
455
|
+
const operation = await this.stopOrReload("reload", { ...opts, force: true }, attestedLegacyUpgrade);
|
|
388
456
|
if (!operation.ok) return { outcome: "failed", operation };
|
|
389
457
|
const after = await this.status();
|
|
390
458
|
return after.health === "running" ? { outcome: "ready", operation } : { outcome: "failed", operation };
|
|
@@ -394,7 +462,11 @@ export class TelegramDaemonController implements BuiltInDaemonController {
|
|
|
394
462
|
return this.stopOrReload("stop", opts);
|
|
395
463
|
}
|
|
396
464
|
|
|
397
|
-
private async stopOrReload(
|
|
465
|
+
private async stopOrReload(
|
|
466
|
+
action: "stop" | "reload",
|
|
467
|
+
opts: DaemonOperationOptions,
|
|
468
|
+
attestedLegacyUpgrade = false,
|
|
469
|
+
): Promise<DaemonOperationResult> {
|
|
398
470
|
const before = await this.status();
|
|
399
471
|
const warnings: string[] = [];
|
|
400
472
|
if (before.runtime.warning) warnings.push(before.runtime.warning);
|
|
@@ -409,6 +481,16 @@ export class TelegramDaemonController implements BuiltInDaemonController {
|
|
|
409
481
|
const killTimeoutMs = opts.killTimeoutMs ?? DEFAULT_KILL_TIMEOUT_MS;
|
|
410
482
|
|
|
411
483
|
const state = await readDaemonState(this.settings, this.fsImpl);
|
|
484
|
+
const attestedLegacyOwner = attestedLegacyUpgrade
|
|
485
|
+
? await readAttestedLegacyDaemonOwner({
|
|
486
|
+
settings: this.settings,
|
|
487
|
+
fs: this.fsImpl,
|
|
488
|
+
now: this.now,
|
|
489
|
+
pidIncarnation: this.deps.pidIncarnation,
|
|
490
|
+
tokenFingerprint: fp,
|
|
491
|
+
chatId,
|
|
492
|
+
})
|
|
493
|
+
: undefined;
|
|
412
494
|
const replaceableLiveOwner =
|
|
413
495
|
(action === "reload" &&
|
|
414
496
|
state !== undefined &&
|
|
@@ -427,6 +509,7 @@ export class TelegramDaemonController implements BuiltInDaemonController {
|
|
|
427
509
|
pidAlive: this.pidAlive,
|
|
428
510
|
pidIncarnation: this.deps.pidIncarnation,
|
|
429
511
|
})) ||
|
|
512
|
+
(action === "reload" && attestedLegacyOwner !== undefined && this.pidAlive(attestedLegacyOwner.state.pid)) ||
|
|
430
513
|
// A physically-live matching owner whose heartbeat is stale (hung) may be
|
|
431
514
|
// past-TTL yet still holding the poller. Autostart/generation-upgrade reloads
|
|
432
515
|
// stay conservative and refuse it, but an explicit `reload --force` must be
|
|
@@ -483,16 +566,18 @@ export class TelegramDaemonController implements BuiltInDaemonController {
|
|
|
483
566
|
}
|
|
484
567
|
|
|
485
568
|
// Running owner: capture identity, request cooperative stop, signal, wait.
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
569
|
+
const modernCapturedOwner =
|
|
570
|
+
hasSafeDaemonStateShape(state) &&
|
|
571
|
+
isSignalableMatchingOwner({
|
|
489
572
|
state,
|
|
490
573
|
tokenFingerprint: fp,
|
|
491
574
|
chatId,
|
|
492
575
|
pidAlive: this.pidAlive,
|
|
493
576
|
pidIncarnation: this.deps.pidIncarnation,
|
|
494
577
|
})
|
|
495
|
-
|
|
578
|
+
? state
|
|
579
|
+
: undefined;
|
|
580
|
+
if (!modernCapturedOwner && !attestedLegacyOwner) {
|
|
496
581
|
return this.result(
|
|
497
582
|
action,
|
|
498
583
|
false,
|
|
@@ -502,7 +587,7 @@ export class TelegramDaemonController implements BuiltInDaemonController {
|
|
|
502
587
|
warnings,
|
|
503
588
|
);
|
|
504
589
|
}
|
|
505
|
-
const capturedOwner = state;
|
|
590
|
+
const capturedOwner = modernCapturedOwner ?? attestedLegacyOwner!.state;
|
|
506
591
|
const oldOwnerId = capturedOwner.ownerId;
|
|
507
592
|
const oldPid = capturedOwner.pid;
|
|
508
593
|
const requestId = this.deps.randomId?.() ?? `${this.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
@@ -511,7 +596,21 @@ export class TelegramDaemonController implements BuiltInDaemonController {
|
|
|
511
596
|
{ version: 1, requestId, action, ownerId: oldOwnerId, pid: oldPid, createdAt: this.now() },
|
|
512
597
|
this.fsImpl,
|
|
513
598
|
);
|
|
514
|
-
|
|
599
|
+
const signalResult = attestedLegacyOwner
|
|
600
|
+
? await this.#signalAttestedLegacyOwner(attestedLegacyOwner, fp, chatId, "SIGTERM")
|
|
601
|
+
: await this.signalCapturedOwner(modernCapturedOwner!, fp, chatId, "SIGTERM");
|
|
602
|
+
if (signalResult === "hard_termination") {
|
|
603
|
+
await this.clearOwnRequest(requestId);
|
|
604
|
+
return this.result(
|
|
605
|
+
action,
|
|
606
|
+
false,
|
|
607
|
+
"telegram daemon has hard process authority; refusing cooperative SIGTERM",
|
|
608
|
+
before,
|
|
609
|
+
await this.status(),
|
|
610
|
+
warnings,
|
|
611
|
+
);
|
|
612
|
+
}
|
|
613
|
+
if (signalResult === "ownership_changed") {
|
|
515
614
|
await this.clearOwnRequest(requestId);
|
|
516
615
|
return this.result(
|
|
517
616
|
action,
|
|
@@ -559,7 +658,9 @@ export class TelegramDaemonController implements BuiltInDaemonController {
|
|
|
559
658
|
// the captured owner/pid still matches, so we never kill a different owner.
|
|
560
659
|
const stillSameOwner = current !== undefined && current.ownerId === oldOwnerId && current.pid === oldPid;
|
|
561
660
|
if (opts.force && stillSameOwner) {
|
|
562
|
-
const killResult =
|
|
661
|
+
const killResult = attestedLegacyOwner
|
|
662
|
+
? await this.#signalAttestedLegacyOwner(attestedLegacyOwner, fp, chatId, "SIGKILL")
|
|
663
|
+
: await this.signalCapturedOwner(modernCapturedOwner!, fp, chatId, "SIGKILL");
|
|
563
664
|
// Kill only a still-live matching owner; an owner that exited between the
|
|
564
665
|
// graceful timeout and this recheck ("already_gone") is confirmed dead by
|
|
565
666
|
// waitForPidDeath, while a real ownership change stays fenced.
|
|
@@ -585,6 +686,17 @@ export class TelegramDaemonController implements BuiltInDaemonController {
|
|
|
585
686
|
return this.result(action, true, "stopped telegram daemon", before, after, warnings);
|
|
586
687
|
}
|
|
587
688
|
|
|
689
|
+
if (!this.#currentConfigurationMatches(fp, chatId)) {
|
|
690
|
+
const after = await this.status();
|
|
691
|
+
return this.result(
|
|
692
|
+
action,
|
|
693
|
+
false,
|
|
694
|
+
"telegram notification configuration changed during reload; refusing to spawn",
|
|
695
|
+
before,
|
|
696
|
+
after,
|
|
697
|
+
warnings,
|
|
698
|
+
);
|
|
699
|
+
}
|
|
588
700
|
const { spawned, ready } = await this.spawnAndWait(roots, fp, chatId);
|
|
589
701
|
warnings.push(...spawned.warnings);
|
|
590
702
|
const after = await this.status();
|