@gajae-code/coding-agent 0.12.6 → 0.12.8
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 +22 -1
- package/dist/types/async/job-manager.d.ts +36 -1
- package/dist/types/commands/notify.d.ts +4 -0
- package/dist/types/config/file-lock.d.ts +3 -0
- package/dist/types/config/model-discovery-manager.d.ts +6 -1
- package/dist/types/config/model-registry.d.ts +2 -0
- package/dist/types/config/resolve-config-value.d.ts +2 -1
- package/dist/types/config/settings-schema.d.ts +17 -1
- package/dist/types/eval/backend.d.ts +2 -2
- package/dist/types/exec/bash-executor.d.ts +8 -10
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +0 -36
- package/dist/types/internal-urls/registry-helpers.d.ts +0 -18
- package/dist/types/main.d.ts +1 -0
- package/dist/types/modes/components/hook-selector.d.ts +20 -0
- package/dist/types/modes/components/notifications-settings-editor.d.ts +53 -4
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +4 -2
- package/dist/types/sdk/broker/broker.d.ts +5 -1
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +1 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +11 -3
- package/dist/types/sdk/bus/config.d.ts +75 -113
- package/dist/types/sdk/bus/discord-live-provider.d.ts +8 -2
- package/dist/types/sdk/bus/discord-provider.d.ts +19 -0
- package/dist/types/sdk/bus/index.d.ts +1 -1
- package/dist/types/sdk/bus/notification-orchestration.d.ts +127 -18
- package/dist/types/sdk/bus/notification-service.d.ts +31 -4
- package/dist/types/sdk/bus/session-control.d.ts +8 -3
- package/dist/types/sdk/bus/slack-live-provider.d.ts +9 -2
- package/dist/types/sdk/bus/slack-provider.d.ts +22 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +9 -4
- package/dist/types/sdk/client/discovery.d.ts +2 -1
- package/dist/types/sdk/host/host.d.ts +2 -23
- package/dist/types/sdk/host/query/handlers.d.ts +2 -0
- package/dist/types/sdk/host/reverse-leases.d.ts +4 -7
- package/dist/types/sdk/index.d.ts +1 -0
- package/dist/types/sdk/providers.d.ts +22 -0
- package/dist/types/sdk/session-directory.d.ts +1 -1
- package/dist/types/session/artifacts.d.ts +5 -8
- package/dist/types/session/client-bridge.d.ts +0 -14
- package/dist/types/session/internal/managed-session-scope.d.ts +17 -1
- package/dist/types/session/internal/managed-session-storage.d.ts +11 -4
- package/dist/types/session/internal/native-publish-outcome.d.ts +1 -0
- package/dist/types/session/internal/session-open-errors.d.ts +5 -0
- package/dist/types/session/messages.d.ts +1 -4
- package/dist/types/session/session-manager.d.ts +7 -6
- package/dist/types/session/session-storage.d.ts +2 -0
- package/dist/types/session/streaming-output.d.ts +0 -32
- package/dist/types/task/receipt.d.ts +1 -2
- package/dist/types/task/types.d.ts +43 -178
- package/dist/types/tools/bash.d.ts +1 -1
- package/dist/types/tools/fetch.d.ts +0 -3
- package/dist/types/tools/output-meta.d.ts +5 -37
- package/dist/types/tools/subagent.d.ts +15 -0
- package/dist/types/tools/todo-write.d.ts +3 -0
- package/dist/types/tools/tool-result.d.ts +0 -1
- package/package.json +10 -8
- package/scripts/build-sdk-package-smoke.ts +51 -0
- package/scripts/capture-notifications-settings-showcase.ts +1 -1
- package/src/async/job-manager.ts +430 -46
- package/src/cli/notify-cli.ts +204 -94
- package/src/commands/notify.ts +19 -8
- package/src/config/file-lock.ts +6 -0
- package/src/config/model-discovery-manager.ts +50 -8
- package/src/config/model-registry.ts +862 -75
- package/src/config/resolve-config-value.ts +10 -8
- package/src/config/settings-schema.ts +25 -15
- package/src/config/settings.ts +15 -15
- package/src/cursor.ts +13 -20
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +2 -3
- package/src/eval/backend.ts +2 -2
- package/src/exec/bash-executor.ts +151 -425
- package/src/gjc-runtime/session-state-sidecar.ts +12 -2
- package/src/gjc-runtime/ultragoal-runtime.ts +3 -280
- package/src/gjc-runtime/workflow-manifest.generated.json +1 -40
- package/src/gjc-runtime/workflow-manifest.ts +0 -12
- package/src/internal-urls/agent-protocol.ts +38 -98
- package/src/internal-urls/artifact-protocol.ts +17 -28
- package/src/internal-urls/docs-index.generated.ts +7 -7
- package/src/internal-urls/registry-helpers.ts +0 -62
- package/src/main.ts +43 -7
- package/src/modes/DESIGN.md +11 -11
- package/src/modes/acp/acp-agent.ts +0 -24
- package/src/modes/acp/acp-client-bridge.ts +11 -154
- package/src/modes/components/hook-selector.ts +155 -44
- package/src/modes/components/model-selector.ts +242 -49
- package/src/modes/components/notifications-settings-editor.ts +631 -85
- package/src/modes/controllers/extension-ui-controller.ts +114 -15
- package/src/modes/controllers/selector-controller.ts +464 -50
- package/src/prompts/tools/bash.md +2 -2
- package/src/sdk/acp/adapter.ts +9 -200
- package/src/sdk/broker/broker.ts +54 -10
- package/src/sdk/broker/lifecycle-ledger.ts +34 -1
- package/src/sdk/broker/lifecycle.ts +47 -50
- package/src/sdk/bus/chat-daemon-cli.ts +12 -5
- package/src/sdk/bus/chat-daemon-control.ts +35 -8
- package/src/sdk/bus/chat-daemon-runtime.ts +16 -2
- package/src/sdk/bus/config.ts +534 -286
- package/src/sdk/bus/discord-live-provider.ts +57 -2
- package/src/sdk/bus/discord-provider.ts +21 -0
- package/src/sdk/bus/index.ts +105 -359
- package/src/sdk/bus/notification-orchestration.ts +371 -68
- package/src/sdk/bus/notification-service.ts +431 -130
- package/src/sdk/bus/session-control.ts +119 -44
- package/src/sdk/bus/slack-live-provider.ts +78 -2
- package/src/sdk/bus/slack-provider.ts +24 -0
- package/src/sdk/bus/telegram-daemon-cli.ts +9 -2
- package/src/sdk/bus/telegram-daemon-contract.ts +9 -4
- package/src/sdk/bus/telegram-daemon-control.ts +3 -3
- package/src/sdk/bus/telegram-daemon.ts +23 -15
- package/src/sdk/client/discovery.ts +10 -4
- package/src/sdk/host/host.ts +9 -187
- package/src/sdk/host/query/handlers.ts +20 -1
- package/src/sdk/host/reverse-leases.ts +42 -249
- package/src/sdk/index.ts +1 -0
- package/src/sdk/protocol/operation-inventory.generated.json +18 -0
- package/src/sdk/protocol/operation-registry.ts +5 -2
- package/src/sdk/providers.ts +51 -0
- package/src/sdk/session-directory.ts +1 -0
- package/src/sdk/session.ts +10 -7
- package/src/session/agent-session.ts +59 -202
- package/src/session/artifacts.ts +74 -211
- package/src/session/client-bridge.ts +0 -15
- package/src/session/internal/managed-session-scope.ts +981 -450
- package/src/session/internal/managed-session-storage.ts +362 -124
- package/src/session/internal/native-publish-outcome.ts +3 -1
- package/src/session/internal/session-open-errors.ts +11 -0
- package/src/session/messages.ts +7 -32
- package/src/session/session-manager.ts +48 -15
- package/src/session/session-storage.ts +72 -5
- package/src/session/streaming-output.ts +40 -198
- package/src/slash-commands/builtin-registry.ts +82 -4
- package/src/task/index.ts +237 -65
- package/src/task/receipt.ts +2 -4
- package/src/task/render.ts +0 -30
- package/src/task/types.ts +10 -34
- package/src/tools/ask.ts +1 -1
- package/src/tools/bash.ts +239 -818
- package/src/tools/eval.ts +4 -99
- package/src/tools/fetch.ts +15 -77
- package/src/tools/find.ts +3 -7
- package/src/tools/output-meta.ts +114 -696
- package/src/tools/search.ts +4 -9
- package/src/tools/subagent.ts +97 -75
- package/src/tools/telegram-send.ts +4 -2
- package/src/tools/todo-write.ts +47 -0
- package/src/tools/tool-result.ts +0 -1
- package/dist/types/gjc-runtime/ultragoal-review-source.d.ts +0 -83
- package/dist/types/utils/artifact-id.d.ts +0 -2
- package/src/gjc-runtime/ultragoal-review-source.ts +0 -378
- package/src/utils/artifact-id.ts +0 -6
|
@@ -2,7 +2,6 @@ import type { AgentToolUpdateCallback } from "@gajae-code/agent-core";
|
|
|
2
2
|
|
|
3
3
|
import { sanitizeText } from "@gajae-code/utils";
|
|
4
4
|
import { formatBytes } from "../tools/render-utils";
|
|
5
|
-
import { isValidArtifactId } from "../utils/artifact-id";
|
|
6
5
|
import { sanitizeWithOptionalSixelPassthrough } from "../utils/sixel";
|
|
7
6
|
|
|
8
7
|
function sanitizeOutputChunk(rawChunk: string): string {
|
|
@@ -50,47 +49,6 @@ export type TerminalArtifactPublisher = (
|
|
|
50
49
|
info: { totalBytes: number; omittedBytes: number },
|
|
51
50
|
) => Promise<TerminalArtifactPublishResult>;
|
|
52
51
|
|
|
53
|
-
const TERMINAL_ARTIFACT_PUBLISH_WAIT_MS = 500;
|
|
54
|
-
const TERMINAL_ARTIFACT_PENDING_LIMIT = 64;
|
|
55
|
-
const pendingTerminalArtifactPublishesByOwner = new WeakMap<object, Set<Promise<TerminalArtifactPublishResult>>>();
|
|
56
|
-
|
|
57
|
-
function pendingTerminalArtifactPublishesForOwner(owner: object): Set<Promise<TerminalArtifactPublishResult>> {
|
|
58
|
-
let pending = pendingTerminalArtifactPublishesByOwner.get(owner);
|
|
59
|
-
if (!pending) {
|
|
60
|
-
pending = new Set();
|
|
61
|
-
pendingTerminalArtifactPublishesByOwner.set(owner, pending);
|
|
62
|
-
}
|
|
63
|
-
return pending;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function terminalPublisherOwner(publisher: TerminalArtifactPublisher): object {
|
|
67
|
-
return (publisher as TerminalArtifactPublisher & { owner?: object }).owner ?? publisher;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async function publishTerminalArtifactBounded(
|
|
71
|
-
publisher: TerminalArtifactPublisher,
|
|
72
|
-
content: string,
|
|
73
|
-
info: { totalBytes: number; omittedBytes: number },
|
|
74
|
-
): Promise<TerminalArtifactPublishResult> {
|
|
75
|
-
const pendingTerminalArtifactPublishes = pendingTerminalArtifactPublishesForOwner(terminalPublisherOwner(publisher));
|
|
76
|
-
if (pendingTerminalArtifactPublishes.size >= TERMINAL_ARTIFACT_PENDING_LIMIT) {
|
|
77
|
-
return { status: "failed", diagnostic: "pending publisher limit reached for this session" };
|
|
78
|
-
}
|
|
79
|
-
const promise = Promise.resolve().then(() => publisher(content, info));
|
|
80
|
-
pendingTerminalArtifactPublishes.add(promise);
|
|
81
|
-
void promise.then(
|
|
82
|
-
() => pendingTerminalArtifactPublishes.delete(promise),
|
|
83
|
-
() => pendingTerminalArtifactPublishes.delete(promise),
|
|
84
|
-
);
|
|
85
|
-
return Promise.race([
|
|
86
|
-
promise,
|
|
87
|
-
Bun.sleep(TERMINAL_ARTIFACT_PUBLISH_WAIT_MS).then(() => ({
|
|
88
|
-
status: "failed" as const,
|
|
89
|
-
diagnostic: `did not settle within ${TERMINAL_ARTIFACT_PUBLISH_WAIT_MS}ms`,
|
|
90
|
-
})),
|
|
91
|
-
]);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
52
|
// =============================================================================
|
|
95
53
|
// Interfaces
|
|
96
54
|
// =============================================================================
|
|
@@ -106,60 +64,18 @@ export interface OutputSummary {
|
|
|
106
64
|
elidedBytes?: number;
|
|
107
65
|
/** Lines elided from the middle when head-retain mode is active. */
|
|
108
66
|
elidedLines?: number;
|
|
109
|
-
/** Exact source ranges retained by head/tail middle-elision windows. */
|
|
110
|
-
headRange?: { start: number; end: number };
|
|
111
|
-
tailRange?: { start: number; end: number };
|
|
112
|
-
/** The retained tail ends with only a byte fragment of its source line. */
|
|
113
|
-
lastLinePartial?: boolean;
|
|
114
|
-
/** The retained head ends with only a byte fragment of its source line. */
|
|
115
|
-
firstLinePartial?: boolean;
|
|
116
67
|
/** Bytes dropped by the per-line column cap (sum across all lines). */
|
|
117
68
|
columnDroppedBytes?: number;
|
|
118
69
|
/** Number of distinct lines that hit the per-line column cap. */
|
|
119
70
|
columnTruncatedLines?: number;
|
|
120
|
-
/** Configured per-line byte/column cap that caused visible omission. */
|
|
121
|
-
columnMax?: number;
|
|
122
71
|
/** Artifact ID for internal URL access (artifact://<id>) when output was persisted. */
|
|
123
72
|
artifactId?: string;
|
|
124
|
-
/** Artifact existence was proven by successful publication or file finalization. */
|
|
125
|
-
artifactVerified?: boolean;
|
|
126
73
|
/** Bytes omitted from artifact storage after the artifact hard cap was reached. */
|
|
127
74
|
artifactTruncatedBytes?: number;
|
|
128
|
-
/** Bytes dropped before the Bash executor received the native output stream. */
|
|
129
|
-
sourceTruncatedBytes?: number;
|
|
130
|
-
/** Exact source capture completeness could not be proven. */
|
|
131
|
-
sourceCaptureIncomplete?: boolean;
|
|
132
75
|
/** Bounded diagnostic when artifact writer or terminal publisher creation, write, finalization, or publication failed. */
|
|
133
76
|
artifactFailureDiagnostic?: string;
|
|
134
77
|
}
|
|
135
78
|
|
|
136
|
-
const kCurrentExecutionArtifactProof = Symbol("OutputSink.CurrentExecutionArtifactProof");
|
|
137
|
-
interface CurrentExecutionArtifactProof {
|
|
138
|
-
artifactId: string;
|
|
139
|
-
payload: string;
|
|
140
|
-
consumed: boolean;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/** Verify that this exact OutputSink summary came from a successful current-execution publication. */
|
|
144
|
-
export function hasCurrentExecutionArtifactProof(
|
|
145
|
-
summary: OutputSummary,
|
|
146
|
-
artifactId: string,
|
|
147
|
-
payload?: string,
|
|
148
|
-
): boolean {
|
|
149
|
-
const proof = (summary as OutputSummary & { [kCurrentExecutionArtifactProof]?: CurrentExecutionArtifactProof })[
|
|
150
|
-
kCurrentExecutionArtifactProof
|
|
151
|
-
];
|
|
152
|
-
if (
|
|
153
|
-
!proof ||
|
|
154
|
-
proof.artifactId !== artifactId ||
|
|
155
|
-
proof.consumed ||
|
|
156
|
-
(payload !== undefined && proof.payload !== payload)
|
|
157
|
-
)
|
|
158
|
-
return false;
|
|
159
|
-
proof.consumed = true;
|
|
160
|
-
return true;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
79
|
export interface OutputSinkOptions {
|
|
164
80
|
/**
|
|
165
81
|
* Deprecated managed artifact pathname. Bare paths are deliberately ignored:
|
|
@@ -225,10 +141,7 @@ export interface TruncationResult {
|
|
|
225
141
|
elidedBytes?: number;
|
|
226
142
|
/** Lines elided from the middle (truncateMiddle only). */
|
|
227
143
|
elidedLines?: number;
|
|
228
|
-
headRange?: { start: number; end: number };
|
|
229
|
-
tailRange?: { start: number; end: number };
|
|
230
144
|
lastLinePartial?: boolean;
|
|
231
|
-
firstLinePartial?: boolean;
|
|
232
145
|
firstLineExceedsLimit?: boolean;
|
|
233
146
|
lastLineExceedsLimit?: boolean;
|
|
234
147
|
}
|
|
@@ -880,7 +793,7 @@ export function truncateMiddle(content: string, options: TruncationOptions = {})
|
|
|
880
793
|
const head = windows.head;
|
|
881
794
|
const tail = windows.tail;
|
|
882
795
|
if (!head || !tail) return noTruncResult(content, windows.totalLines, windows.totalBytes);
|
|
883
|
-
if (head.lines + tail.lines >= windows.totalLines
|
|
796
|
+
if (head.lines + tail.lines >= windows.totalLines)
|
|
884
797
|
return noTruncResult(content, windows.totalLines, windows.totalBytes);
|
|
885
798
|
const legacyElidedLines = Math.max(0, windows.totalLines - head.lines - tail.lines);
|
|
886
799
|
const legacyElidedBytes =
|
|
@@ -889,7 +802,7 @@ export function truncateMiddle(content: string, options: TruncationOptions = {})
|
|
|
889
802
|
: windows.elidedBytes;
|
|
890
803
|
const marker = formatMiddleElisionMarker(legacyElidedLines, legacyElidedBytes);
|
|
891
804
|
const markerBytes = Buffer.byteLength(marker, "utf-8");
|
|
892
|
-
|
|
805
|
+
return {
|
|
893
806
|
content: `${head.content}\n${marker}\n${tail.content}`,
|
|
894
807
|
truncated: true,
|
|
895
808
|
truncatedBy: "middle",
|
|
@@ -903,11 +816,6 @@ export function truncateMiddle(content: string, options: TruncationOptions = {})
|
|
|
903
816
|
firstLineExceedsLimit: false,
|
|
904
817
|
...(tail.lastLinePartial ? { lastLineExceedsLimit: true } : {}),
|
|
905
818
|
};
|
|
906
|
-
Object.defineProperties(result, {
|
|
907
|
-
headRange: { value: { start: head.origin.startLine, end: head.origin.endLine } },
|
|
908
|
-
tailRange: { value: { start: tail.origin.startLine, end: tail.origin.endLine } },
|
|
909
|
-
});
|
|
910
|
-
return result;
|
|
911
819
|
}
|
|
912
820
|
}
|
|
913
821
|
}
|
|
@@ -1024,8 +932,6 @@ export class OutputSink {
|
|
|
1024
932
|
#head = "";
|
|
1025
933
|
#headBytes = 0;
|
|
1026
934
|
#headRetentionDisabled = false;
|
|
1027
|
-
#headEndsMidLine = false;
|
|
1028
|
-
#tailStartsMidLine = false;
|
|
1029
935
|
#totalLines = 0; // newline count
|
|
1030
936
|
#totalBytes = 0;
|
|
1031
937
|
#processedBytes = 0;
|
|
@@ -1061,8 +967,6 @@ export class OutputSink {
|
|
|
1061
967
|
#pendingArtifactContent?: string[];
|
|
1062
968
|
#artifactPublishAttempted = false;
|
|
1063
969
|
#finalized = false;
|
|
1064
|
-
#finalizing = false;
|
|
1065
|
-
#artifactFinalization?: Promise<string | undefined>;
|
|
1066
970
|
|
|
1067
971
|
#fileReady = false;
|
|
1068
972
|
|
|
@@ -1116,10 +1020,9 @@ export class OutputSink {
|
|
|
1116
1020
|
return this.#buffer;
|
|
1117
1021
|
}
|
|
1118
1022
|
|
|
1119
|
-
#setTail(text: string, bytes = Buffer.byteLength(text, "utf-8")
|
|
1023
|
+
#setTail(text: string, bytes = Buffer.byteLength(text, "utf-8")): void {
|
|
1120
1024
|
this.#buffer = text;
|
|
1121
1025
|
this.#bufferBytes = bytes;
|
|
1122
|
-
this.#tailStartsMidLine = startsMidLine;
|
|
1123
1026
|
}
|
|
1124
1027
|
|
|
1125
1028
|
#appendTail(text: string, bytes: number): void {
|
|
@@ -1134,12 +1037,9 @@ export class OutputSink {
|
|
|
1134
1037
|
|
|
1135
1038
|
#trimTailTo(maxBytes: number): void {
|
|
1136
1039
|
if (this.#bufferBytes <= maxBytes) return;
|
|
1137
|
-
const
|
|
1138
|
-
const { text, bytes } = truncateTailBytes(previous, maxBytes);
|
|
1139
|
-
const removed = previous.slice(0, previous.length - text.length);
|
|
1040
|
+
const { text, bytes } = truncateTailBytes(this.#buffer, maxBytes);
|
|
1140
1041
|
this.#buffer = text;
|
|
1141
1042
|
this.#bufferBytes = bytes;
|
|
1142
|
-
this.#tailStartsMidLine = removed.length > 0 && !removed.endsWith("\n");
|
|
1143
1043
|
}
|
|
1144
1044
|
|
|
1145
1045
|
/**
|
|
@@ -1150,7 +1050,6 @@ export class OutputSink {
|
|
|
1150
1050
|
// F21: with coalescing enabled, accumulate raw chunks and process them in batches; the default
|
|
1151
1051
|
// (disabled) path calls #ingest directly and is byte-identical to the historical per-chunk path.
|
|
1152
1052
|
push(chunk: string): void {
|
|
1153
|
-
if (this.#finalized || this.#finalizing) return;
|
|
1154
1053
|
if (!this.#coalesceSanitize) {
|
|
1155
1054
|
this.#ingest(chunk);
|
|
1156
1055
|
return;
|
|
@@ -1233,10 +1132,6 @@ export class OutputSink {
|
|
|
1233
1132
|
}
|
|
1234
1133
|
}
|
|
1235
1134
|
|
|
1236
|
-
if (this.#headBytes > 0 && this.#bufferBytes === 0 && tailBytes > 0) {
|
|
1237
|
-
this.#headEndsMidLine = !this.#head.endsWith("\n") && !tailChunk.startsWith("\n");
|
|
1238
|
-
this.#tailStartsMidLine = this.#headEndsMidLine;
|
|
1239
|
-
}
|
|
1240
1135
|
this.#pushTail(tailChunk, tailBytes);
|
|
1241
1136
|
}
|
|
1242
1137
|
|
|
@@ -1321,8 +1216,7 @@ export class OutputSink {
|
|
|
1321
1216
|
// Avoid creating a giant intermediate string when chunk alone dominates.
|
|
1322
1217
|
if (dataBytes >= threshold) {
|
|
1323
1218
|
const { text, bytes } = truncateTailBytes(chunk, threshold);
|
|
1324
|
-
|
|
1325
|
-
this.#setTail(text, bytes, removed.length > 0 && !removed.endsWith("\n"));
|
|
1219
|
+
this.#setTail(text, bytes);
|
|
1326
1220
|
} else {
|
|
1327
1221
|
// Intermediate size is bounded (<= threshold + dataBytes), safe to concat.
|
|
1328
1222
|
this.#appendTail(chunk, dataBytes);
|
|
@@ -1496,19 +1390,13 @@ export class OutputSink {
|
|
|
1496
1390
|
return this.#publishedArtifactId;
|
|
1497
1391
|
}
|
|
1498
1392
|
this.#artifactPublishAttempted = true;
|
|
1499
|
-
this.#finalizing = true;
|
|
1500
1393
|
const content = this.#pendingArtifactContent?.join("") ?? "";
|
|
1501
1394
|
const omittedBytes = this.#artifactTruncatedBytes;
|
|
1502
1395
|
try {
|
|
1503
|
-
const published = await
|
|
1396
|
+
const published = await this.#artifactPublisher!(content, {
|
|
1504
1397
|
totalBytes: this.#totalBytes,
|
|
1505
1398
|
omittedBytes,
|
|
1506
1399
|
});
|
|
1507
|
-
if (typeof published !== "object" || published === null || !("status" in published)) {
|
|
1508
|
-
this.#recordTerminalArtifactDiagnostic("failed", "publisher returned an invalid result");
|
|
1509
|
-
this.#artifactFinalizationFailed = true;
|
|
1510
|
-
return undefined;
|
|
1511
|
-
}
|
|
1512
1400
|
if (published.status === "unavailable") {
|
|
1513
1401
|
this.#recordTerminalArtifactDiagnostic("unavailable");
|
|
1514
1402
|
this.#artifactFinalizationFailed = true;
|
|
@@ -1519,13 +1407,8 @@ export class OutputSink {
|
|
|
1519
1407
|
this.#artifactFinalizationFailed = true;
|
|
1520
1408
|
return undefined;
|
|
1521
1409
|
}
|
|
1522
|
-
if (published.
|
|
1523
|
-
this.#recordTerminalArtifactDiagnostic("failed", "
|
|
1524
|
-
this.#artifactFinalizationFailed = true;
|
|
1525
|
-
return undefined;
|
|
1526
|
-
}
|
|
1527
|
-
if (!isValidArtifactId(published.artifactId)) {
|
|
1528
|
-
this.#recordTerminalArtifactDiagnostic("failed", "storage returned an invalid artifact id");
|
|
1410
|
+
if (published.artifactId.length === 0) {
|
|
1411
|
+
this.#recordTerminalArtifactDiagnostic("failed", "storage returned no artifact id");
|
|
1529
1412
|
this.#artifactFinalizationFailed = true;
|
|
1530
1413
|
return undefined;
|
|
1531
1414
|
}
|
|
@@ -1549,48 +1432,6 @@ export class OutputSink {
|
|
|
1549
1432
|
}
|
|
1550
1433
|
}
|
|
1551
1434
|
|
|
1552
|
-
#shouldFinalizeArtifact(): boolean {
|
|
1553
|
-
return (
|
|
1554
|
-
this.#finalized ||
|
|
1555
|
-
this.#artifactFinalizationFailed ||
|
|
1556
|
-
this.#shouldPublishTerminalArtifact() ||
|
|
1557
|
-
this.#file !== undefined ||
|
|
1558
|
-
(this.#artifactPath !== undefined && this.#artifactTruncatedBytes > 0)
|
|
1559
|
-
);
|
|
1560
|
-
}
|
|
1561
|
-
|
|
1562
|
-
async #finalizeArtifact(): Promise<string | undefined> {
|
|
1563
|
-
let artifactId: string | undefined;
|
|
1564
|
-
if (this.#finalized) artifactId = this.#publishedArtifactId ?? this.#artifactId;
|
|
1565
|
-
else if (!this.#artifactFinalizationFailed) {
|
|
1566
|
-
if (this.#artifactPublisher && !this.#artifactPath) artifactId = await this.#publishTerminalArtifact();
|
|
1567
|
-
if (!this.#artifactFinalizationFailed && this.#artifactPath) {
|
|
1568
|
-
if (this.#artifactTruncatedBytes > 0) this.#createFileSink();
|
|
1569
|
-
if (this.#file) {
|
|
1570
|
-
this.#finalizing = true;
|
|
1571
|
-
await this.#writeArtifactTruncationNotice();
|
|
1572
|
-
try {
|
|
1573
|
-
await this.#file.sink.end();
|
|
1574
|
-
artifactId = this.#file.artifactId;
|
|
1575
|
-
this.#finalized = true;
|
|
1576
|
-
} catch (error) {
|
|
1577
|
-
this.#recordArtifactFailure("end", error);
|
|
1578
|
-
this.#artifactFinalizationFailed = true;
|
|
1579
|
-
this.#file = undefined;
|
|
1580
|
-
this.#fileReady = false;
|
|
1581
|
-
}
|
|
1582
|
-
}
|
|
1583
|
-
}
|
|
1584
|
-
}
|
|
1585
|
-
if (this.#finalized) {
|
|
1586
|
-
this.#pendingFileWrites = undefined;
|
|
1587
|
-
this.#pendingFileWriteBytes = 0;
|
|
1588
|
-
this.#fileReady = false;
|
|
1589
|
-
this.#pendingArtifactContent = undefined;
|
|
1590
|
-
}
|
|
1591
|
-
return artifactId;
|
|
1592
|
-
}
|
|
1593
|
-
|
|
1594
1435
|
createInput(): WritableStream<Uint8Array | string> {
|
|
1595
1436
|
const dec = new TextDecoder("utf-8", { ignoreBOM: true });
|
|
1596
1437
|
const finalize = () => {
|
|
@@ -1616,20 +1457,17 @@ export class OutputSink {
|
|
|
1616
1457
|
* append directly to the tail without reordering the replacement.
|
|
1617
1458
|
*/
|
|
1618
1459
|
replace(text: string): void {
|
|
1619
|
-
if (this.#finalized || this.#finalizing) return;
|
|
1620
1460
|
this.#coalesceBuf = "";
|
|
1621
1461
|
const replacementBytes = Buffer.byteLength(text, "utf-8");
|
|
1622
1462
|
this.#head = "";
|
|
1623
1463
|
this.#headBytes = 0;
|
|
1624
1464
|
this.#setTail("");
|
|
1625
|
-
this.#headEndsMidLine = false;
|
|
1626
1465
|
|
|
1627
1466
|
if (this.#headLimit > 0) {
|
|
1628
1467
|
const head = truncateHeadBytes(text, this.#headLimit);
|
|
1629
1468
|
this.#appendHead(head.text, head.bytes);
|
|
1630
1469
|
const tailText = text.substring(head.text.length);
|
|
1631
|
-
this.#
|
|
1632
|
-
this.#setTail(tailText, replacementBytes - head.bytes, this.#headEndsMidLine);
|
|
1470
|
+
this.#setTail(tailText, replacementBytes - head.bytes);
|
|
1633
1471
|
} else {
|
|
1634
1472
|
this.#setTail(text, replacementBytes);
|
|
1635
1473
|
}
|
|
@@ -1653,9 +1491,35 @@ export class OutputSink {
|
|
|
1653
1491
|
const totalLines = this.#sawData ? this.#totalLines + 1 : 0;
|
|
1654
1492
|
|
|
1655
1493
|
let artifactId: string | undefined;
|
|
1656
|
-
if (this.#
|
|
1657
|
-
this.#
|
|
1658
|
-
|
|
1494
|
+
if (this.#finalized) {
|
|
1495
|
+
artifactId = this.#publishedArtifactId ?? this.#artifactId;
|
|
1496
|
+
} else if (!this.#artifactFinalizationFailed) {
|
|
1497
|
+
if (this.#artifactPublisher && !this.#artifactPath) {
|
|
1498
|
+
artifactId = await this.#publishTerminalArtifact();
|
|
1499
|
+
}
|
|
1500
|
+
if (!this.#artifactFinalizationFailed && this.#artifactPath) {
|
|
1501
|
+
if (this.#artifactTruncatedBytes > 0) this.#createFileSink();
|
|
1502
|
+
await this.#writeArtifactTruncationNotice();
|
|
1503
|
+
if (this.#file) {
|
|
1504
|
+
try {
|
|
1505
|
+
await this.#file.sink.end();
|
|
1506
|
+
artifactId = this.#file.artifactId;
|
|
1507
|
+
this.#finalized = true;
|
|
1508
|
+
} catch (error) {
|
|
1509
|
+
this.#recordArtifactFailure("end", error);
|
|
1510
|
+
this.#artifactFinalizationFailed = true;
|
|
1511
|
+
this.#file = undefined;
|
|
1512
|
+
this.#fileReady = false;
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
if (this.#finalized) {
|
|
1518
|
+
// Terminal: the artifact is closed; replay state is no longer needed.
|
|
1519
|
+
this.#pendingFileWrites = undefined;
|
|
1520
|
+
this.#pendingFileWriteBytes = 0;
|
|
1521
|
+
this.#fileReady = false;
|
|
1522
|
+
this.#pendingArtifactContent = undefined;
|
|
1659
1523
|
}
|
|
1660
1524
|
// Non-finalized dumps (no artifact sink ever opened) keep the raw replay
|
|
1661
1525
|
// queue so a later post-dump push that spills produces a CUMULATIVE
|
|
@@ -1679,8 +1543,6 @@ export class OutputSink {
|
|
|
1679
1543
|
let outputLines: number;
|
|
1680
1544
|
let elidedBytes: number | undefined;
|
|
1681
1545
|
let elidedLines: number | undefined;
|
|
1682
|
-
let headRange: { start: number; end: number } | undefined;
|
|
1683
|
-
let tailRange: { start: number; end: number } | undefined;
|
|
1684
1546
|
|
|
1685
1547
|
if (headBytes > 0 && effectiveTotalBytes > headBytes + tailBytes) {
|
|
1686
1548
|
// Middle was elided. Emit both explicit windows, including when they are
|
|
@@ -1693,12 +1555,7 @@ export class OutputSink {
|
|
|
1693
1555
|
const tailSep = tailBuf.startsWith("\n") ? "" : "\n";
|
|
1694
1556
|
body = `${headText}${headSep}${marker}${tailSep}${tailBuf}`;
|
|
1695
1557
|
outputBytes = headBytes + markerBytes + tailBytes + headSep.length + tailSep.length;
|
|
1696
|
-
outputLines =
|
|
1697
|
-
headRange = headLines > 0 && !this.#headEndsMidLine ? { start: 1, end: headLines } : undefined;
|
|
1698
|
-
tailRange =
|
|
1699
|
-
tailLines > 0 && !this.#tailStartsMidLine
|
|
1700
|
-
? { start: processedTotalLines - tailLines + 1, end: processedTotalLines }
|
|
1701
|
-
: undefined;
|
|
1558
|
+
outputLines = headLines + 1 + tailLines;
|
|
1702
1559
|
this.#truncated = true;
|
|
1703
1560
|
} else if (headBytes > 0) {
|
|
1704
1561
|
// Head + tail combine into the full buffered output (no overlap or elision).
|
|
@@ -1712,7 +1569,7 @@ export class OutputSink {
|
|
|
1712
1569
|
}
|
|
1713
1570
|
|
|
1714
1571
|
if (this.#columnDroppedBytes > 0) this.#truncated = true;
|
|
1715
|
-
|
|
1572
|
+
return {
|
|
1716
1573
|
output: `${noticeLine}${body}`,
|
|
1717
1574
|
truncated: this.#truncated,
|
|
1718
1575
|
totalLines,
|
|
@@ -1721,27 +1578,12 @@ export class OutputSink {
|
|
|
1721
1578
|
outputBytes,
|
|
1722
1579
|
elidedBytes,
|
|
1723
1580
|
elidedLines,
|
|
1724
|
-
headRange,
|
|
1725
|
-
tailRange,
|
|
1726
|
-
firstLinePartial: this.#headEndsMidLine || undefined,
|
|
1727
|
-
lastLinePartial: this.#tailStartsMidLine || undefined,
|
|
1728
1581
|
columnDroppedBytes: this.#columnDroppedBytes > 0 ? this.#columnDroppedBytes : undefined,
|
|
1729
1582
|
columnTruncatedLines: this.#columnTruncatedLines > 0 ? this.#columnTruncatedLines : undefined,
|
|
1730
|
-
columnMax: this.#columnDroppedBytes > 0 ? this.#maxColumns : undefined,
|
|
1731
1583
|
artifactTruncatedBytes: this.#artifactTruncatedBytes > 0 ? this.#artifactTruncatedBytes : undefined,
|
|
1732
1584
|
artifactFailureDiagnostic: this.#artifactFailureDiagnostic,
|
|
1733
1585
|
artifactId,
|
|
1734
|
-
artifactVerified: artifactId !== undefined && this.#publishedArtifactId === artifactId,
|
|
1735
1586
|
};
|
|
1736
|
-
if (summary.artifactVerified === true && artifactId !== undefined) {
|
|
1737
|
-
Object.defineProperty(summary, kCurrentExecutionArtifactProof, {
|
|
1738
|
-
value: { artifactId, payload: summary.output, consumed: false } satisfies CurrentExecutionArtifactProof,
|
|
1739
|
-
enumerable: true,
|
|
1740
|
-
configurable: false,
|
|
1741
|
-
writable: false,
|
|
1742
|
-
});
|
|
1743
|
-
}
|
|
1744
|
-
return summary;
|
|
1745
1587
|
}
|
|
1746
1588
|
}
|
|
1747
1589
|
|
|
@@ -29,6 +29,8 @@ import {
|
|
|
29
29
|
canApplyComposerSubmission,
|
|
30
30
|
type InteractiveModeContext,
|
|
31
31
|
} from "../modes/types";
|
|
32
|
+
import { ChatDaemonController } from "../sdk/bus/chat-daemon-control";
|
|
33
|
+
import type { NotificationProvider } from "../sdk/bus/config";
|
|
32
34
|
import {
|
|
33
35
|
buildNotificationStatusReport,
|
|
34
36
|
checkNotificationHealth,
|
|
@@ -39,6 +41,7 @@ import {
|
|
|
39
41
|
recoverNotifications,
|
|
40
42
|
sendNotificationTest,
|
|
41
43
|
} from "../sdk/bus/notification-service";
|
|
44
|
+
import { TelegramDaemonController } from "../sdk/bus/telegram-daemon-control";
|
|
42
45
|
import { computeCacheMissCostSummary, formatCacheMissSummaryLines } from "../session/cache-economics";
|
|
43
46
|
import { formatModelOnboardingGuidance } from "../setup/model-onboarding-guidance";
|
|
44
47
|
import {
|
|
@@ -498,6 +501,48 @@ function buildChangelogCommandOutput(showFull: boolean): string {
|
|
|
498
501
|
return `${title}\n\n${changelogMarkdown}${hint}`;
|
|
499
502
|
}
|
|
500
503
|
|
|
504
|
+
type NotifyServiceArgs = { provider?: NotificationProvider; probe: boolean; message?: string } | { error: string };
|
|
505
|
+
|
|
506
|
+
function isNotificationProvider(value: string): value is NotificationProvider {
|
|
507
|
+
return value === "telegram" || value === "discord" || value === "slack";
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function parseNotifyServiceArgs(input: string, allowMessage: boolean): NotifyServiceArgs {
|
|
511
|
+
const tokens = input.trim().split(/\s+/).filter(Boolean);
|
|
512
|
+
let provider: NotificationProvider | undefined;
|
|
513
|
+
let probe = false;
|
|
514
|
+
const message: string[] = [];
|
|
515
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
516
|
+
const token = tokens[index]!;
|
|
517
|
+
if (token === "--probe") {
|
|
518
|
+
if (allowMessage) return { error: "--probe is valid only for /notify health." };
|
|
519
|
+
probe = true;
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
if (token === "--provider" || token.startsWith("--provider=")) {
|
|
523
|
+
const value = token === "--provider" ? tokens[++index] : token.slice("--provider=".length);
|
|
524
|
+
if (!value || !isNotificationProvider(value)) {
|
|
525
|
+
return { error: "--provider must be telegram, discord, or slack." };
|
|
526
|
+
}
|
|
527
|
+
if (provider && provider !== value) return { error: "Conflicting notification providers were supplied." };
|
|
528
|
+
provider = value;
|
|
529
|
+
continue;
|
|
530
|
+
}
|
|
531
|
+
if (!provider && message.length === 0 && isNotificationProvider(token)) {
|
|
532
|
+
provider = token;
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
if (token.startsWith("--")) return { error: `Unknown notification option: ${token}` };
|
|
536
|
+
message.push(token);
|
|
537
|
+
}
|
|
538
|
+
if (!allowMessage && message.length > 0) return { error: "Health accepts only a provider and --probe." };
|
|
539
|
+
return {
|
|
540
|
+
...(provider ? { provider } : {}),
|
|
541
|
+
probe,
|
|
542
|
+
...(message.length > 0 ? { message: message.join(" ") } : {}),
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
|
|
501
546
|
const shutdownHandlerTui = (_command: ParsedSlashCommand, runtime: TuiSlashCommandRuntime): SlashCommandResult => {
|
|
502
547
|
runtime.ctx.editor.setText("");
|
|
503
548
|
void runtime.ctx.shutdown();
|
|
@@ -514,8 +559,12 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
514
559
|
{ name: "on", description: "Enable notifications for this session" },
|
|
515
560
|
{ name: "off", description: "Disable notifications for this session" },
|
|
516
561
|
{ name: "status", description: "Show notification configuration (no secrets)" },
|
|
517
|
-
{
|
|
518
|
-
|
|
562
|
+
{
|
|
563
|
+
name: "health",
|
|
564
|
+
description: "Config, ownership, endpoint, and selected-provider health",
|
|
565
|
+
usage: "[provider] [--probe]",
|
|
566
|
+
},
|
|
567
|
+
{ name: "test", description: "Send a test notification", usage: "[provider|--provider provider] [message]" },
|
|
519
568
|
{ name: "recovery", description: "Clear dead-owner locks and stale endpoint files" },
|
|
520
569
|
{ name: "setup", description: "How to pair a Telegram bot (run in a terminal)" },
|
|
521
570
|
],
|
|
@@ -541,12 +590,41 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
541
590
|
await runtime.output(formatNotificationStatusReport(buildNotificationStatusReport(runtime.settings)));
|
|
542
591
|
return commandConsumed();
|
|
543
592
|
case "health": {
|
|
544
|
-
const
|
|
593
|
+
const parsed = parseNotifyServiceArgs(rest, false);
|
|
594
|
+
if ("error" in parsed) {
|
|
595
|
+
return usage(`Usage: /notify health [telegram|discord|slack] [--probe]\n${parsed.error}`, runtime);
|
|
596
|
+
}
|
|
597
|
+
const report = await checkNotificationHealth({
|
|
598
|
+
settings: runtime.settings,
|
|
599
|
+
stateRoot,
|
|
600
|
+
provider: parsed.provider,
|
|
601
|
+
probe: parsed.probe,
|
|
602
|
+
});
|
|
545
603
|
await runtime.output(formatNotificationHealthReport(report));
|
|
546
604
|
return commandConsumed();
|
|
547
605
|
}
|
|
548
606
|
case "test": {
|
|
549
|
-
const
|
|
607
|
+
const parsed = parseNotifyServiceArgs(rest, true);
|
|
608
|
+
if ("error" in parsed) {
|
|
609
|
+
return usage(
|
|
610
|
+
`Usage: /notify test [telegram|discord|slack|--provider provider] [message]\n${parsed.error}`,
|
|
611
|
+
runtime,
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
const result = await sendNotificationTest({
|
|
615
|
+
settings: runtime.settings,
|
|
616
|
+
provider: parsed.provider,
|
|
617
|
+
text: parsed.message,
|
|
618
|
+
deps: {
|
|
619
|
+
providerRuntimeStatus: async provider => {
|
|
620
|
+
const status =
|
|
621
|
+
provider === "telegram"
|
|
622
|
+
? await new TelegramDaemonController(runtime.settings).status()
|
|
623
|
+
: await new ChatDaemonController(runtime.settings, provider).status();
|
|
624
|
+
return status.health === "running" ? "ready" : "inactive";
|
|
625
|
+
},
|
|
626
|
+
},
|
|
627
|
+
});
|
|
550
628
|
await runtime.output(formatNotificationTestResult(result));
|
|
551
629
|
return commandConsumed();
|
|
552
630
|
}
|