@gajae-code/coding-agent 0.18.5 → 0.18.7
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 +140 -0
- package/dist/types/capability/types.d.ts +4 -0
- package/dist/types/cli-commands.d.ts +10 -0
- package/dist/types/cli-ordinary.d.ts +13 -0
- package/dist/types/cli.d.ts +12 -3
- package/dist/types/commands/contribution-prep.d.ts +0 -3
- package/dist/types/commands/sdk.d.ts +3 -0
- package/dist/types/commands/update.d.ts +1 -0
- package/dist/types/config/model-profile-activation.d.ts +19 -0
- package/dist/types/config/settings.d.ts +4 -0
- package/dist/types/coordinator-mcp/session-reaper.d.ts +5 -0
- package/dist/types/discovery/helpers.d.ts +2 -2
- package/dist/types/edit/modes/replace.d.ts +3 -3
- package/dist/types/edit/streaming.d.ts +2 -2
- package/dist/types/exec/bash-shell-supervisor.d.ts +12 -0
- package/dist/types/runtime-mcp/elicitation.d.ts +12 -0
- package/dist/types/sdk/broker/discovery.d.ts +8 -0
- package/dist/types/sdk/broker/ensure.d.ts +1 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +4 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +9 -0
- package/dist/types/sdk/broker/runtime.d.ts +1 -1
- package/dist/types/sdk/broker/task-artifact-owner-validation.d.ts +55 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +4 -2
- package/dist/types/sdk/cli/diagnostics-cli.d.ts +18 -0
- package/dist/types/sdk/diagnostics/observe-broker.d.ts +185 -0
- package/dist/types/sdk/host/control/operations.d.ts +1 -1
- package/dist/types/sdk/host/host.d.ts +6 -0
- package/dist/types/sdk/index.d.ts +1 -0
- package/dist/types/sdk/router/session-router.d.ts +9 -0
- package/dist/types/sdk/session.d.ts +13 -0
- package/dist/types/session/agent-session.d.ts +5 -1
- package/dist/types/session/internal/managed-session-storage.d.ts +11 -1
- package/dist/types/session/internal/task-artifact-owner-access.d.ts +17 -0
- package/dist/types/session/internal/task-artifact-owner-transcript.d.ts +3 -0
- package/dist/types/session/session-retirement.d.ts +47 -0
- package/dist/types/session/session-storage.d.ts +0 -46
- package/dist/types/session/session-transcript-header.d.ts +2 -0
- package/dist/types/session/task-artifact-owner-codec.d.ts +107 -0
- package/dist/types/session/task-artifact-owner-retirement.d.ts +7 -0
- package/dist/types/session/task-artifact-owner.d.ts +10 -0
- package/dist/types/tools/ask-contract.d.ts +75 -254
- package/dist/types/tools/bash.d.ts +1 -1
- package/package.json +7 -7
- package/scripts/build-sdk-package-smoke.ts +5 -0
- package/scripts/resident-memory-bench.ts +174 -20
- package/src/capability/index.ts +1 -0
- package/src/capability/types.ts +4 -0
- package/src/cli/config-cli.ts +8 -18
- package/src/cli/public-command-entry.ts +5 -0
- package/src/cli/ssh-cli.ts +6 -3
- package/src/cli/web-search-cli.ts +9 -3
- package/src/cli-commands.ts +14 -0
- package/src/cli-main.ts +7 -8
- package/src/cli-ordinary.ts +197 -0
- package/src/cli.ts +25 -182
- package/src/commands/contribution-prep.ts +1 -3
- package/src/commands/sdk.ts +171 -21
- package/src/commands/update.ts +15 -1
- package/src/config/file-lock.ts +91 -2
- package/src/config/model-profile-activation.ts +70 -4
- package/src/config/model-profile-contract.ts +4 -1
- package/src/config/model-profiles.ts +23 -31
- package/src/config/model-resolver.ts +9 -1
- package/src/config/settings.ts +44 -4
- package/src/coordinator-mcp/event-webhook.ts +19 -14
- package/src/coordinator-mcp/policy.ts +14 -4
- package/src/coordinator-mcp/server.ts +142 -7
- package/src/coordinator-mcp/session-reaper.ts +9 -1
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
- package/src/discovery/helpers.ts +9 -3
- package/src/discovery/ssh.ts +8 -3
- package/src/edit/modes/replace.ts +6 -6
- package/src/edit/streaming.ts +15 -15
- package/src/exec/bash-shell-supervisor.ts +60 -3
- package/src/gjc-runtime/gc-runtime.ts +2 -1
- package/src/hindsight/config.ts +9 -3
- package/src/internal-urls/docs-index.generated.ts +11 -10
- package/src/memories/index.ts +8 -2
- package/src/modes/acp/acp-agent.ts +724 -370
- package/src/modes/components/tool-execution.ts +1 -1
- package/src/prompts/tools/ask.md +2 -0
- package/src/runtime-mcp/client.ts +13 -4
- package/src/runtime-mcp/elicitation.ts +142 -0
- package/src/sdk/broker/broker.ts +54 -1
- package/src/sdk/broker/daemon-entry.ts +67 -6
- package/src/sdk/broker/discovery.ts +8 -0
- package/src/sdk/broker/ensure.ts +156 -20
- package/src/sdk/broker/lifecycle-ledger.ts +110 -12
- package/src/sdk/broker/lifecycle.ts +334 -348
- package/src/sdk/broker/runtime.ts +5 -1
- package/src/sdk/broker/task-artifact-owner-validation.ts +259 -0
- package/src/sdk/broker/transport.ts +5 -1
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +3 -0
- package/src/sdk/bus/index.ts +3 -3
- package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
- package/src/sdk/bus/telegram-daemon-control.ts +15 -12
- package/src/sdk/bus/telegram-daemon.ts +38 -3
- package/src/sdk/cli/diagnostics-cli.ts +247 -0
- package/src/sdk/diagnostics/observe-broker.ts +654 -0
- package/src/sdk/host/control/dispatch.ts +52 -4
- package/src/sdk/host/control/operations.ts +1 -1
- package/src/sdk/host/host.ts +6 -0
- package/src/sdk/host/session-runtime.test.ts +83 -4
- package/src/sdk/host/session-runtime.ts +15 -7
- package/src/sdk/index.ts +1 -0
- package/src/sdk/lifecycle-session.ts +1 -0
- package/src/sdk/mcp/server.ts +15 -3
- package/src/sdk/protocol/operation-registry.ts +1 -1
- package/src/sdk/router/session-router.ts +26 -3
- package/src/sdk/session.ts +51 -8
- package/src/session/agent-session.ts +169 -43
- package/src/session/blob-store.ts +78 -33
- package/src/session/internal/managed-session-storage.ts +203 -4
- package/src/session/internal/task-artifact-owner-access.ts +229 -0
- package/src/session/internal/task-artifact-owner-transcript.ts +68 -0
- package/src/session/session-retirement.ts +155 -0
- package/src/session/session-storage.ts +1 -160
- package/src/session/session-transcript-header.ts +17 -0
- package/src/session/task-artifact-owner-codec.ts +578 -0
- package/src/session/task-artifact-owner-retirement.ts +475 -0
- package/src/session/task-artifact-owner.ts +289 -0
- package/src/setup/hermes/templates/operator-instructions.v1.md +19 -1
- package/src/setup/hermes-setup.ts +4 -1
- package/src/slash-commands/builtin-registry.ts +8 -5
- package/src/tools/ask-contract.test.ts +76 -1
- package/src/tools/ask-contract.ts +57 -6
- package/src/tools/ask.ts +16 -7
- package/src/tools/bash.ts +38 -29
- package/src/tools/descriptors.test.ts +29 -0
- package/src/tools/tool-catalog.generated.ts +7 -4
- package/src/web/search/index.ts +2 -2
|
@@ -6,7 +6,11 @@ import { currentExecutablePath } from "@gajae-code/natives";
|
|
|
6
6
|
|
|
7
7
|
import internalSourceMarker from "./internal-source-marker-2178.txt" with { type: "file" };
|
|
8
8
|
|
|
9
|
-
export type SdkInternalAction =
|
|
9
|
+
export type SdkInternalAction =
|
|
10
|
+
| "broker-internal"
|
|
11
|
+
| "broker-trampoline-internal"
|
|
12
|
+
| "session-host-internal"
|
|
13
|
+
| "stderr-drain-internal";
|
|
10
14
|
|
|
11
15
|
export type SdkInternalSpawnCommand =
|
|
12
16
|
| {
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import type { NativeExactUnlinkResult } from "@gajae-code/natives";
|
|
2
|
+
import {
|
|
3
|
+
assertSessionRoot,
|
|
4
|
+
parseTaskArtifactOwnerDeletionEvidence,
|
|
5
|
+
parseTaskArtifactOwnerRetirementContinuation,
|
|
6
|
+
parseTaskArtifactOwnerRetirementOutcome,
|
|
7
|
+
type TaskArtifactOwnerDeletionEvidence,
|
|
8
|
+
type TaskArtifactOwnerRetirementContinuation,
|
|
9
|
+
type TaskArtifactOwnerRetirementOutcome,
|
|
10
|
+
type TaskArtifactOwnerStorageContext,
|
|
11
|
+
} from "../../session/task-artifact-owner-codec";
|
|
12
|
+
|
|
13
|
+
export type BrokerCleanupPhase = "artifacts" | "transcript" | "metadata" | "lifecycle";
|
|
14
|
+
|
|
15
|
+
/** Exact owner-disposition wire shape. Evidence and continuation are supplied separately by cleanup replay. */
|
|
16
|
+
export type BrokerTaskArtifactOwnerRetirementDisposition =
|
|
17
|
+
| { readonly kind: "completed"; readonly nativeOutcome?: NativeExactUnlinkResult }
|
|
18
|
+
| {
|
|
19
|
+
readonly kind: "payload_retired";
|
|
20
|
+
readonly namespace: "retained";
|
|
21
|
+
readonly nativeOutcome: NativeExactUnlinkResult;
|
|
22
|
+
}
|
|
23
|
+
| { readonly kind: "cleanup_pending"; readonly nativeOutcome?: NativeExactUnlinkResult }
|
|
24
|
+
| { readonly kind: "uncertain"; readonly reason: string; readonly nativeOutcome?: NativeExactUnlinkResult };
|
|
25
|
+
|
|
26
|
+
/** Owner-only projection of durable cleanup state accepted by the pure validator. */
|
|
27
|
+
export interface BrokerTaskArtifactOwnerCleanupFields {
|
|
28
|
+
readonly taskArtifactOwnerDeletionEvidence?: unknown;
|
|
29
|
+
readonly taskArtifactOwnerRetirementContinuation?: unknown;
|
|
30
|
+
readonly taskArtifactOwnerRetirementOutcome?: unknown;
|
|
31
|
+
readonly taskArtifactOwnerPayloadRetired?: unknown;
|
|
32
|
+
readonly taskArtifactOwnerNamespaceRetained?: unknown;
|
|
33
|
+
readonly taskArtifactOwnerRetired?: unknown;
|
|
34
|
+
readonly taskArtifactOwnerTranscriptDeleted?: unknown;
|
|
35
|
+
readonly taskArtifactOwnerCleanupError?: unknown;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Fully decoded owner-only cleanup state for an authorized session and phase. */
|
|
39
|
+
export interface BrokerTaskArtifactOwnerCleanupValidation {
|
|
40
|
+
readonly deletionEvidence?: TaskArtifactOwnerDeletionEvidence;
|
|
41
|
+
readonly retirementContinuation?: TaskArtifactOwnerRetirementContinuation;
|
|
42
|
+
readonly retirementOutcome?: TaskArtifactOwnerRetirementOutcome;
|
|
43
|
+
readonly payloadRetired?: true;
|
|
44
|
+
readonly namespaceRetained?: true;
|
|
45
|
+
readonly retired?: true;
|
|
46
|
+
readonly transcriptDeleted?: true;
|
|
47
|
+
readonly cleanupDiagnostic?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
51
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function hasExactKeys(value: Record<string, unknown>, keys: readonly string[]): boolean {
|
|
55
|
+
return Object.keys(value).length === keys.length && keys.every(key => Object.hasOwn(value, key));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function invalidDisposition(): never {
|
|
59
|
+
throw new Error("task_artifact_owner_retirement_disposition_invalid");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Decode the owner-only wire disposition using its exact trusted evidence and continuation.
|
|
64
|
+
* A decoded `completed` value is historical DTO data, not proof of physical retirement or writer closure.
|
|
65
|
+
*/
|
|
66
|
+
export function parseBrokerTaskArtifactOwnerRetirementDisposition(
|
|
67
|
+
value: unknown,
|
|
68
|
+
context: TaskArtifactOwnerStorageContext,
|
|
69
|
+
evidence: TaskArtifactOwnerDeletionEvidence,
|
|
70
|
+
continuation: TaskArtifactOwnerRetirementContinuation | undefined,
|
|
71
|
+
): TaskArtifactOwnerRetirementOutcome {
|
|
72
|
+
assertSessionRoot(context);
|
|
73
|
+
if (!isRecord(value)) return invalidDisposition();
|
|
74
|
+
const kind = value.kind;
|
|
75
|
+
let expectedKeys: readonly string[];
|
|
76
|
+
switch (kind) {
|
|
77
|
+
case "completed":
|
|
78
|
+
case "cleanup_pending":
|
|
79
|
+
expectedKeys = Object.hasOwn(value, "nativeOutcome") ? ["kind", "nativeOutcome"] : ["kind"];
|
|
80
|
+
break;
|
|
81
|
+
case "payload_retired":
|
|
82
|
+
expectedKeys = ["kind", "namespace", "nativeOutcome"];
|
|
83
|
+
break;
|
|
84
|
+
case "uncertain":
|
|
85
|
+
expectedKeys = Object.hasOwn(value, "nativeOutcome")
|
|
86
|
+
? ["kind", "reason", "nativeOutcome"]
|
|
87
|
+
: ["kind", "reason"];
|
|
88
|
+
break;
|
|
89
|
+
default:
|
|
90
|
+
return invalidDisposition();
|
|
91
|
+
}
|
|
92
|
+
if (!hasExactKeys(value, expectedKeys)) return invalidDisposition();
|
|
93
|
+
const reconstructed = {
|
|
94
|
+
...value,
|
|
95
|
+
evidence,
|
|
96
|
+
...(continuation !== undefined ? { continuation } : {}),
|
|
97
|
+
};
|
|
98
|
+
return parseTaskArtifactOwnerRetirementOutcome(context, evidence, reconstructed);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function serializeNativeOutcome(outcome: NativeExactUnlinkResult): NativeExactUnlinkResult {
|
|
102
|
+
return Object.freeze({
|
|
103
|
+
ok: outcome.ok,
|
|
104
|
+
...(outcome.code !== undefined ? { code: outcome.code } : {}),
|
|
105
|
+
...(outcome.detachedPath !== undefined ? { detachedPath: outcome.detachedPath } : {}),
|
|
106
|
+
...(outcome.retainedSuccessorPath !== undefined ? { retainedSuccessorPath: outcome.retainedSuccessorPath } : {}),
|
|
107
|
+
...(outcome.retainedPlaceholderPath !== undefined
|
|
108
|
+
? { retainedPlaceholderPath: outcome.retainedPlaceholderPath }
|
|
109
|
+
: {}),
|
|
110
|
+
...(outcome.retainedUnknownPath !== undefined ? { retainedUnknownPath: outcome.retainedUnknownPath } : {}),
|
|
111
|
+
...(outcome.payloadDurable !== undefined ? { payloadDurable: outcome.payloadDurable } : {}),
|
|
112
|
+
...(outcome.windowsErrorCode !== undefined ? { windowsErrorCode: outcome.windowsErrorCode } : {}),
|
|
113
|
+
}) as NativeExactUnlinkResult;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Project an already validated internal outcome to the exact broker wire disposition. */
|
|
117
|
+
export function serializeBrokerTaskArtifactOwnerRetirementDisposition(
|
|
118
|
+
outcome: TaskArtifactOwnerRetirementOutcome,
|
|
119
|
+
): BrokerTaskArtifactOwnerRetirementDisposition {
|
|
120
|
+
switch (outcome.kind) {
|
|
121
|
+
case "completed":
|
|
122
|
+
return Object.freeze({
|
|
123
|
+
kind: outcome.kind,
|
|
124
|
+
...(outcome.nativeOutcome ? { nativeOutcome: serializeNativeOutcome(outcome.nativeOutcome) } : {}),
|
|
125
|
+
});
|
|
126
|
+
case "payload_retired":
|
|
127
|
+
return Object.freeze({
|
|
128
|
+
kind: outcome.kind,
|
|
129
|
+
namespace: outcome.namespace,
|
|
130
|
+
nativeOutcome: serializeNativeOutcome(outcome.nativeOutcome),
|
|
131
|
+
});
|
|
132
|
+
case "cleanup_pending":
|
|
133
|
+
return Object.freeze({
|
|
134
|
+
kind: outcome.kind,
|
|
135
|
+
...(outcome.nativeOutcome ? { nativeOutcome: serializeNativeOutcome(outcome.nativeOutcome) } : {}),
|
|
136
|
+
});
|
|
137
|
+
case "uncertain":
|
|
138
|
+
return Object.freeze({
|
|
139
|
+
kind: outcome.kind,
|
|
140
|
+
reason: outcome.reason,
|
|
141
|
+
...(outcome.nativeOutcome ? { nativeOutcome: serializeNativeOutcome(outcome.nativeOutcome) } : {}),
|
|
142
|
+
});
|
|
143
|
+
default: {
|
|
144
|
+
const exhaustive: never = outcome;
|
|
145
|
+
throw new Error(`task_artifact_owner_retirement_outcome_unknown:${String(exhaustive)}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const OWNER_CLEANUP_KEYS = new Set([
|
|
151
|
+
"taskArtifactOwnerDeletionEvidence",
|
|
152
|
+
"taskArtifactOwnerRetirementContinuation",
|
|
153
|
+
"taskArtifactOwnerRetirementOutcome",
|
|
154
|
+
"taskArtifactOwnerPayloadRetired",
|
|
155
|
+
"taskArtifactOwnerNamespaceRetained",
|
|
156
|
+
"taskArtifactOwnerRetired",
|
|
157
|
+
"taskArtifactOwnerTranscriptDeleted",
|
|
158
|
+
"taskArtifactOwnerCleanupError",
|
|
159
|
+
]);
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Strictly decode the owner-field projection of cleanup state against trusted session, scope context, phase,
|
|
163
|
+
* and artifact-removal state. Empty/legacy owner fields decode to an empty result. A decoded completion
|
|
164
|
+
* remains historical data: any later effect must separately verify current physical absence, fresh scrub
|
|
165
|
+
* proof, native success and current owner-parent/side-path authority before it mutates storage or transcripts.
|
|
166
|
+
*/
|
|
167
|
+
export function decodeBrokerTaskArtifactOwnerCleanupFields(
|
|
168
|
+
value: unknown,
|
|
169
|
+
context: TaskArtifactOwnerStorageContext,
|
|
170
|
+
expectedSessionId: string,
|
|
171
|
+
phase: BrokerCleanupPhase,
|
|
172
|
+
artifactsRemoved: boolean | undefined,
|
|
173
|
+
): BrokerTaskArtifactOwnerCleanupValidation {
|
|
174
|
+
assertSessionRoot(context);
|
|
175
|
+
if (!isRecord(value) || Object.keys(value).some(key => !OWNER_CLEANUP_KEYS.has(key)))
|
|
176
|
+
throw new Error("task_artifact_owner_cleanup_fields_invalid");
|
|
177
|
+
if (expectedSessionId.length === 0 || (artifactsRemoved !== undefined && typeof artifactsRemoved !== "boolean"))
|
|
178
|
+
throw new Error("task_artifact_owner_cleanup_fields_invalid");
|
|
179
|
+
|
|
180
|
+
const has = (key: string) => Object.hasOwn(value, key);
|
|
181
|
+
const trueFlag = (key: string): true | undefined => {
|
|
182
|
+
if (!has(key)) return undefined;
|
|
183
|
+
if (value[key] !== true) throw new Error("task_artifact_owner_cleanup_flags_invalid");
|
|
184
|
+
return true;
|
|
185
|
+
};
|
|
186
|
+
let deletionEvidence: TaskArtifactOwnerDeletionEvidence | undefined;
|
|
187
|
+
if (has("taskArtifactOwnerDeletionEvidence")) {
|
|
188
|
+
deletionEvidence = parseTaskArtifactOwnerDeletionEvidence(value.taskArtifactOwnerDeletionEvidence);
|
|
189
|
+
if (deletionEvidence.sessionId !== expectedSessionId)
|
|
190
|
+
throw new Error("task_artifact_owner_cleanup_session_mismatch");
|
|
191
|
+
}
|
|
192
|
+
let retirementContinuation: TaskArtifactOwnerRetirementContinuation | undefined;
|
|
193
|
+
if (has("taskArtifactOwnerRetirementContinuation")) {
|
|
194
|
+
if (!deletionEvidence) throw new Error("task_artifact_owner_cleanup_evidence_missing");
|
|
195
|
+
retirementContinuation = parseTaskArtifactOwnerRetirementContinuation(
|
|
196
|
+
context,
|
|
197
|
+
deletionEvidence,
|
|
198
|
+
value.taskArtifactOwnerRetirementContinuation,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
let retirementOutcome: TaskArtifactOwnerRetirementOutcome | undefined;
|
|
202
|
+
if (has("taskArtifactOwnerRetirementOutcome")) {
|
|
203
|
+
if (!deletionEvidence) throw new Error("task_artifact_owner_cleanup_evidence_missing");
|
|
204
|
+
retirementOutcome = parseBrokerTaskArtifactOwnerRetirementDisposition(
|
|
205
|
+
value.taskArtifactOwnerRetirementOutcome,
|
|
206
|
+
context,
|
|
207
|
+
deletionEvidence,
|
|
208
|
+
retirementContinuation,
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const payloadRetired = trueFlag("taskArtifactOwnerPayloadRetired");
|
|
213
|
+
const namespaceRetained = trueFlag("taskArtifactOwnerNamespaceRetained");
|
|
214
|
+
const retired = trueFlag("taskArtifactOwnerRetired");
|
|
215
|
+
const transcriptDeleted = trueFlag("taskArtifactOwnerTranscriptDeleted");
|
|
216
|
+
let cleanupDiagnostic: string | undefined;
|
|
217
|
+
if (has("taskArtifactOwnerCleanupError")) {
|
|
218
|
+
const diagnostic = value.taskArtifactOwnerCleanupError;
|
|
219
|
+
if (typeof diagnostic !== "string" || diagnostic.length === 0 || diagnostic.length > 4096)
|
|
220
|
+
throw new Error("task_artifact_owner_cleanup_diagnostic_invalid");
|
|
221
|
+
cleanupDiagnostic = diagnostic;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (
|
|
225
|
+
(retired !== undefined && (payloadRetired !== undefined || namespaceRetained !== undefined)) ||
|
|
226
|
+
(retired !== undefined && retirementOutcome?.kind !== "completed") ||
|
|
227
|
+
(payloadRetired !== undefined) !== (namespaceRetained !== undefined) ||
|
|
228
|
+
((retired !== undefined ||
|
|
229
|
+
payloadRetired !== undefined ||
|
|
230
|
+
namespaceRetained !== undefined ||
|
|
231
|
+
transcriptDeleted !== undefined) &&
|
|
232
|
+
(!deletionEvidence || !retirementOutcome)) ||
|
|
233
|
+
(transcriptDeleted !== undefined &&
|
|
234
|
+
(phase !== "artifacts" || artifactsRemoved !== true || retirementOutcome === undefined))
|
|
235
|
+
)
|
|
236
|
+
throw new Error("task_artifact_owner_cleanup_state_invalid");
|
|
237
|
+
|
|
238
|
+
if (retirementOutcome) {
|
|
239
|
+
if (
|
|
240
|
+
(retired !== undefined && retirementOutcome.kind !== "completed") ||
|
|
241
|
+
(retirementOutcome.kind === "completed" && retired === undefined) ||
|
|
242
|
+
(payloadRetired !== undefined &&
|
|
243
|
+
(retirementOutcome.kind !== "payload_retired" || retirementContinuation === undefined)) ||
|
|
244
|
+
(retirementOutcome.kind === "payload_retired" && payloadRetired === undefined)
|
|
245
|
+
)
|
|
246
|
+
throw new Error("task_artifact_owner_cleanup_disposition_mismatch");
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return Object.freeze({
|
|
250
|
+
deletionEvidence,
|
|
251
|
+
retirementContinuation,
|
|
252
|
+
retirementOutcome,
|
|
253
|
+
payloadRetired,
|
|
254
|
+
namespaceRetained,
|
|
255
|
+
retired,
|
|
256
|
+
transcriptDeleted,
|
|
257
|
+
cleanupDiagnostic,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { logger } from "@gajae-code/utils";
|
|
2
3
|
import type { ServerWebSocket } from "bun";
|
|
3
4
|
import type { Broker } from "./broker";
|
|
4
5
|
|
|
@@ -7,6 +8,8 @@ const PROTOCOL_VERSION = 3;
|
|
|
7
8
|
const MAX_BROKER_JSON_FRAME_BYTES = 4 * 1024 * 1024;
|
|
8
9
|
|
|
9
10
|
const BROKER_OPERATIONS = new Set([
|
|
11
|
+
// Observation-only published diagnostics; it starts, ensures and recovers nothing.
|
|
12
|
+
"broker.diagnostics",
|
|
10
13
|
"session.list",
|
|
11
14
|
"session.get_endpoint",
|
|
12
15
|
"session.create",
|
|
@@ -160,7 +163,8 @@ export class BrokerTransport {
|
|
|
160
163
|
try {
|
|
161
164
|
const result = await this.#broker.handleRequest(frame.operation, frame.input, frame.idempotencyKey);
|
|
162
165
|
send(socket, { type: "broker_response", id: frame.id, ...result });
|
|
163
|
-
} catch {
|
|
166
|
+
} catch (error) {
|
|
167
|
+
logger.warn(`sdk broker request failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
164
168
|
sendError(socket, frame.id, "unavailable", "broker request failed");
|
|
165
169
|
}
|
|
166
170
|
}
|
|
@@ -141,8 +141,8 @@ export type ChatDaemonAction = "stop" | "reload";
|
|
|
141
141
|
* crashes, recovery, and attachment retirement cannot replay ambiguous work.
|
|
142
142
|
*/
|
|
143
143
|
export const CHAT_DAEMON_GENERATIONS: Readonly<Record<ChatDaemonKind, number>> = {
|
|
144
|
-
discord:
|
|
145
|
-
slack:
|
|
144
|
+
discord: 83,
|
|
145
|
+
slack: 90,
|
|
146
146
|
};
|
|
147
147
|
|
|
148
148
|
export function chatDaemonGeneration(kind: ChatDaemonKind): number {
|
|
@@ -318,6 +318,9 @@ export class ChatDaemonRuntime {
|
|
|
318
318
|
this.#router = new SessionRouter({
|
|
319
319
|
agentDir: input.agentDir,
|
|
320
320
|
observer: true,
|
|
321
|
+
// A lean final answer carries no messageRef, so the raw native copy of a
|
|
322
|
+
// positioned frame has no identity to collapse onto and would post twice.
|
|
323
|
+
positionedNotificationEffects: true,
|
|
321
324
|
correlateFrame,
|
|
322
325
|
deps: {
|
|
323
326
|
...deps.routerDeps,
|
package/src/sdk/bus/index.ts
CHANGED
|
@@ -105,6 +105,7 @@ import {
|
|
|
105
105
|
canDeliverSdkEvent,
|
|
106
106
|
createSdkSurfaceFactory,
|
|
107
107
|
masterAttestationForEffectiveHost,
|
|
108
|
+
POSITIONED_NOTIFICATION_EFFECTS_CAPABILITY,
|
|
108
109
|
reattestMasterSessionIdentity,
|
|
109
110
|
SESSION_HOST_OBSERVER_CAPABILITY,
|
|
110
111
|
type SessionSdkHost,
|
|
@@ -204,7 +205,6 @@ import {
|
|
|
204
205
|
ASK_SELECTED_ACK_CAPABILITY,
|
|
205
206
|
type EnsureDaemonResult,
|
|
206
207
|
ensureTelegramDaemonRunningDetailed,
|
|
207
|
-
POSITIONED_NOTIFICATION_EFFECTS_CAPABILITY,
|
|
208
208
|
} from "./telegram-daemon";
|
|
209
209
|
|
|
210
210
|
export type {
|
|
@@ -3722,8 +3722,8 @@ function sdkControlSurface(
|
|
|
3722
3722
|
return unavailable("model.cycle", "no session model-cycle seam is installed")();
|
|
3723
3723
|
return { changed: (await ctx.cycleModel()) !== undefined };
|
|
3724
3724
|
},
|
|
3725
|
-
setThinking: level => {
|
|
3726
|
-
api.
|
|
3725
|
+
setThinking: async level => {
|
|
3726
|
+
await api.setThinkingLevelForControl(level as ThinkingLevel, false);
|
|
3727
3727
|
return { changed: true };
|
|
3728
3728
|
},
|
|
3729
3729
|
cycleThinking: () => {
|
|
@@ -297,7 +297,7 @@ export const SDK_LIFECYCLE_ROUTER_PROTOCOL_VERSION = 1;
|
|
|
297
297
|
* Generation 193 gates streamed content on negotiated observer capabilities,
|
|
298
298
|
* so existing owners are replaced before exposing the new observer path.
|
|
299
299
|
*/
|
|
300
|
-
export const DAEMON_GENERATION =
|
|
300
|
+
export const DAEMON_GENERATION = 200;
|
|
301
301
|
|
|
302
302
|
/**
|
|
303
303
|
* Serving-compatibility boundary for daemon lifecycle requests. Epoch 7
|
|
@@ -53,6 +53,16 @@ const nodeFs: TelegramDaemonFs = {
|
|
|
53
53
|
const DEFAULT_GRACEFUL_TIMEOUT_MS = 8_000;
|
|
54
54
|
const DEFAULT_KILL_TIMEOUT_MS = 3_000;
|
|
55
55
|
const DEFAULT_WAIT_STEP_MS = 25;
|
|
56
|
+
// Windows process handles cannot deliver SIGTERM cooperatively. The request
|
|
57
|
+
// file is therefore the sole notification channel, so a captured owner gets
|
|
58
|
+
// enough time to finish its long poll, network slack, and shutdown joins before
|
|
59
|
+
// an explicit force request may escalate. Short explicit timeouts are clamped
|
|
60
|
+
// to this floor only for hard-termination authorities; POSIX keeps 8 seconds.
|
|
61
|
+
const HARD_TERMINATION_LONG_POLL_MS = 25_000;
|
|
62
|
+
const HARD_TERMINATION_NETWORK_SLACK_MS = 5_000;
|
|
63
|
+
const HARD_TERMINATION_SHUTDOWN_JOINS_MS = 10_000;
|
|
64
|
+
const HARD_TERMINATION_GRACE_MS =
|
|
65
|
+
HARD_TERMINATION_LONG_POLL_MS + HARD_TERMINATION_NETWORK_SLACK_MS + HARD_TERMINATION_SHUTDOWN_JOINS_MS;
|
|
56
66
|
|
|
57
67
|
export interface TelegramDaemonControlRequest {
|
|
58
68
|
version: 1;
|
|
@@ -641,17 +651,6 @@ export class TelegramDaemonController implements BuiltInDaemonController {
|
|
|
641
651
|
const signalResult = attestedLegacyOwner
|
|
642
652
|
? await this.#signalAttestedLegacyOwner(attestedLegacyOwner, fp, chatId, "SIGTERM")
|
|
643
653
|
: await this.signalCapturedOwner(modernCapturedOwner!, fp, chatId, "SIGTERM");
|
|
644
|
-
if (signalResult === "hard_termination") {
|
|
645
|
-
await this.clearOwnRequest(requestId);
|
|
646
|
-
return this.result(
|
|
647
|
-
action,
|
|
648
|
-
false,
|
|
649
|
-
"telegram daemon has hard process authority; refusing cooperative SIGTERM",
|
|
650
|
-
before,
|
|
651
|
-
await this.status(),
|
|
652
|
-
warnings,
|
|
653
|
-
);
|
|
654
|
-
}
|
|
655
654
|
if (signalResult === "ownership_changed") {
|
|
656
655
|
await this.clearOwnRequest(requestId);
|
|
657
656
|
return this.result(
|
|
@@ -667,7 +666,11 @@ export class TelegramDaemonController implements BuiltInDaemonController {
|
|
|
667
666
|
// death through the normal path — waitForPidDeath returns immediately for a
|
|
668
667
|
// dead pid, so stop succeeds and reload proceeds to spawn the replacement.
|
|
669
668
|
|
|
670
|
-
|
|
669
|
+
const cooperativeGraceMs =
|
|
670
|
+
signalResult === "hard_termination"
|
|
671
|
+
? Math.max(gracefulTimeoutMs, HARD_TERMINATION_GRACE_MS)
|
|
672
|
+
: gracefulTimeoutMs;
|
|
673
|
+
let dead = await this.waitForPidDeath(oldPid, cooperativeGraceMs);
|
|
671
674
|
if (!dead) {
|
|
672
675
|
// Old pid still alive after the cooperative SIGTERM. Inspect current ownership.
|
|
673
676
|
const currentSnapshot = await readOwnerFreshnessSnapshot({ settings: this.settings, fs: this.fsImpl });
|
|
@@ -71,6 +71,7 @@ export {
|
|
|
71
71
|
TELEGRAM_TRANSPORT_GENERATION,
|
|
72
72
|
} from "./telegram-daemon-contract";
|
|
73
73
|
|
|
74
|
+
import { POSITIONED_NOTIFICATION_EFFECTS_CAPABILITY } from "../host/host";
|
|
74
75
|
import {
|
|
75
76
|
type AgentDirSessionLifecycleService,
|
|
76
77
|
createSessionLifecycleService,
|
|
@@ -303,8 +304,9 @@ export const ASK_CONTROLS_CAPABILITY = "ask_controls_v1";
|
|
|
303
304
|
export const TOOL_ACTIVITY_CAPABILITY = "tool_activity_v2";
|
|
304
305
|
/** Receive-only compatibility capability for pre-v2 hosts. */
|
|
305
306
|
export const LEGACY_TOOL_ACTIVITY_CAPABILITY = "tool_activity_v1";
|
|
306
|
-
/**
|
|
307
|
-
export
|
|
307
|
+
/** Public `sdk/bus/telegram-daemon` path for the canonical `sdk/host` capability token. */
|
|
308
|
+
export { POSITIONED_NOTIFICATION_EFFECTS_CAPABILITY };
|
|
309
|
+
|
|
308
310
|
type ToolActivityCapability = "v1" | "v2";
|
|
309
311
|
|
|
310
312
|
function negotiateToolActivityCapability(
|
|
@@ -362,6 +364,7 @@ const BOT_API_RETRY_ATTEMPTS = 3;
|
|
|
362
364
|
// Backoff after a failed getUpdates long-poll so a persistent outage does not
|
|
363
365
|
// busy-loop the daemon.
|
|
364
366
|
const POLL_BACKOFF_MS = 1_000;
|
|
367
|
+
const CONTROL_STOP_WATCH_INTERVAL_MS = 750;
|
|
365
368
|
const AUTOMATIC_RELOAD_COOLDOWN_MS = 10 * 60 * 1_000;
|
|
366
369
|
// Default freshness-poll window a cooldown contender waits for a sibling's
|
|
367
370
|
// replacement daemon to publish a fresh ready owner before reloading itself.
|
|
@@ -4567,6 +4570,7 @@ export class TelegramNotificationDaemon {
|
|
|
4567
4570
|
private stopRequested = false;
|
|
4568
4571
|
/** First observed stop cause wins, including an explicit stop. */
|
|
4569
4572
|
#stopCause: TelegramDaemonStopCause | undefined;
|
|
4573
|
+
#controlStopCheck: Promise<boolean> | undefined;
|
|
4570
4574
|
/** Signal cause resolution is asynchronous because control requests are file-backed. */
|
|
4571
4575
|
#stopCauseResolution: Promise<void> | undefined;
|
|
4572
4576
|
|
|
@@ -12875,6 +12879,9 @@ export class TelegramNotificationDaemon {
|
|
|
12875
12879
|
throw new Error("validation forum destination must be the exact supergroup forum returned by getChat");
|
|
12876
12880
|
}
|
|
12877
12881
|
let ownershipProved = false;
|
|
12882
|
+
let controlWatchTimer: Timer | NodeJS.Timeout | undefined;
|
|
12883
|
+
let controlWatchActive = true;
|
|
12884
|
+
const clearTimeoutImpl = this.opts.clearTimeoutImpl ?? clearTimeout;
|
|
12878
12885
|
try {
|
|
12879
12886
|
const renewed = await renewDaemonHeartbeat({
|
|
12880
12887
|
settings: this.opts.settings,
|
|
@@ -12935,6 +12942,19 @@ export class TelegramNotificationDaemon {
|
|
|
12935
12942
|
}
|
|
12936
12943
|
await this.#attachmentRouter.start();
|
|
12937
12944
|
let idleSince = this.runtime.now();
|
|
12945
|
+
const setTimeoutImpl = this.opts.setTimeoutImpl ?? setTimeout;
|
|
12946
|
+
const watchControl = () => {
|
|
12947
|
+
if (!controlWatchActive || !this.running || !this.opts.control) return;
|
|
12948
|
+
controlWatchTimer = setTimeoutImpl(() => {
|
|
12949
|
+
controlWatchTimer = undefined;
|
|
12950
|
+
void this.controlStopRequested()
|
|
12951
|
+
.catch(() => false)
|
|
12952
|
+
.finally(() => {
|
|
12953
|
+
if (controlWatchActive && this.running) watchControl();
|
|
12954
|
+
});
|
|
12955
|
+
}, CONTROL_STOP_WATCH_INTERVAL_MS);
|
|
12956
|
+
};
|
|
12957
|
+
if (this.opts.control) watchControl();
|
|
12938
12958
|
while (this.running) {
|
|
12939
12959
|
if (await this.controlStopRequested()) break;
|
|
12940
12960
|
// A thrown renewal (e.g. a transient Windows EPERM/EACCES/EBUSY while
|
|
@@ -13003,6 +13023,8 @@ export class TelegramNotificationDaemon {
|
|
|
13003
13023
|
this.requestStop("unexpected_exception");
|
|
13004
13024
|
throw error;
|
|
13005
13025
|
} finally {
|
|
13026
|
+
controlWatchActive = false;
|
|
13027
|
+
if (controlWatchTimer !== undefined) clearTimeoutImpl(controlWatchTimer);
|
|
13006
13028
|
this.requestStop();
|
|
13007
13029
|
await this.#stopCauseResolution;
|
|
13008
13030
|
this.running = false;
|
|
@@ -13089,8 +13111,21 @@ export class TelegramNotificationDaemon {
|
|
|
13089
13111
|
private async controlStopRequested(): Promise<boolean> {
|
|
13090
13112
|
if (this.runtime.stopRequested) return true;
|
|
13091
13113
|
if (!this.opts.control) return false;
|
|
13114
|
+
if (this.#controlStopCheck) return this.#controlStopCheck;
|
|
13115
|
+
const check = this.checkControlStopRequested();
|
|
13116
|
+
this.#controlStopCheck = check;
|
|
13117
|
+
try {
|
|
13118
|
+
return await check;
|
|
13119
|
+
} finally {
|
|
13120
|
+
if (this.#controlStopCheck === check) this.#controlStopCheck = undefined;
|
|
13121
|
+
}
|
|
13122
|
+
}
|
|
13123
|
+
|
|
13124
|
+
private async checkControlStopRequested(): Promise<boolean> {
|
|
13092
13125
|
try {
|
|
13093
|
-
const
|
|
13126
|
+
const control = this.opts.control;
|
|
13127
|
+
if (!control) return false;
|
|
13128
|
+
const requested = await control.shouldStop(this.opts.ownerId);
|
|
13094
13129
|
if (!requested) return false;
|
|
13095
13130
|
this.#resolveControlStopCause("stop");
|
|
13096
13131
|
this.requestStop();
|