@segmentstream/agent 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -1
- package/dist/_chunks/{build-metadata-CIFPiRho.mjs → build-metadata-DR4gACLZ.mjs} +1 -1
- package/dist/extension/config.d.ts +14 -0
- package/dist/extension/config.mjs +14 -0
- package/dist/extension/hooks/sandbox.d.ts +9 -0
- package/dist/extension/hooks/sandbox.mjs +12 -0
- package/dist/extension/instructions/sandbox.d.ts +2 -0
- package/dist/extension/instructions/sandbox.mjs +36 -0
- package/dist/extension/lib/attachment-formats.d.ts +2 -0
- package/dist/extension/lib/attachment-formats.mjs +29 -1
- package/dist/extension/lib/attachments.d.ts +4 -0
- package/dist/extension/lib/attachments.mjs +1 -1
- package/dist/extension/lib/build-metadata.mjs +1 -1
- package/dist/extension/lib/conversation-runtime.d.ts +5 -1
- package/dist/extension/lib/conversation-runtime.mjs +31 -5
- package/dist/extension/lib/conversations.d.ts +6 -0
- package/dist/extension/lib/conversations.mjs +4 -1
- package/dist/extension/lib/ontology-arguments.d.ts +28 -0
- package/dist/extension/lib/ontology-arguments.mjs +54 -0
- package/dist/extension/lib/sandbox-session.d.ts +104 -0
- package/dist/extension/lib/sandbox-session.mjs +142 -0
- package/dist/extension/lib/sandbox.d.ts +38 -0
- package/dist/extension/lib/sandbox.mjs +64 -0
- package/dist/extension/lib/telegram/adapter.mjs +11 -4
- package/dist/extension/lib/telegram/conversations.d.ts +16 -0
- package/dist/extension/lib/telegram/conversations.mjs +60 -15
- package/dist/extension/lib/telegram/health.mjs +1 -1
- package/dist/extension/lib/telegram/lifecycle.mjs +1 -1
- package/dist/extension/lib/telegram/silent.d.ts +24 -2
- package/dist/extension/lib/telegram/silent.mjs +30 -4
- package/dist/extension/lib/telegram/turn-errors.d.ts +7 -1
- package/dist/extension/lib/telegram/turn-errors.mjs +89 -76
- package/dist/extension/tools/attachments_promote.d.ts +2 -7
- package/dist/extension/tools/attachments_promote.mjs +34 -10
- package/dist/extension/tools/ontology.d.ts +4 -3
- package/dist/extension/tools/ontology.mjs +10 -3
- package/dist/extension/tools/sandbox.d.ts +7 -0
- package/dist/extension/tools/sandbox.mjs +29 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.mjs +2 -1
- package/package.json +6 -2
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { type StateHandle } from "eve/context";
|
|
2
|
+
import type { RuntimeSandboxSession, SandboxSession } from "eve/sandbox";
|
|
3
|
+
/**
|
|
4
|
+
* The SDK's side of a session's sandbox (`docs/agent.md`, "Sandbox"): the marker its selector
|
|
5
|
+
* writes, the session state recording when it opened, the gate every file tool passes and the
|
|
6
|
+
* cleanup. eve cannot say which environment a session's sandbox came from without opening it, and
|
|
7
|
+
* the configured environment object need not survive bundling, so the SDK's selector writes a
|
|
8
|
+
* marker into the sandbox it opens and the tools refuse a sandbox without one. The marker guards
|
|
9
|
+
* against a missing or replaced `agent/agent/sandbox.ts`; it is not a boundary against the model,
|
|
10
|
+
* which can only reach the sandbox after the gate has passed.
|
|
11
|
+
*/
|
|
12
|
+
/** Where the SDK's selector records that it opened the sandbox. */
|
|
13
|
+
export declare const SANDBOX_MARKER_PATH = "/workspace/.segmentstream/sandbox.json";
|
|
14
|
+
/** Where eve stages the files that reach the model (`eve/docs/sandbox`, "Inbound attachments"). */
|
|
15
|
+
export declare const SANDBOX_ATTACHMENTS_DIRECTORY = "/workspace/.eve/attachments";
|
|
16
|
+
/** A sandbox older than this is replaced on its first file-tool use in a turn. */
|
|
17
|
+
export declare const SANDBOX_MAXIMUM_AGE_MS: number;
|
|
18
|
+
/** What a replacement carries over of the attachments staged within the last day. */
|
|
19
|
+
export declare const CARRIED_ATTACHMENTS: {
|
|
20
|
+
readonly files: 32;
|
|
21
|
+
readonly bytes: number;
|
|
22
|
+
};
|
|
23
|
+
export interface SandboxMarker {
|
|
24
|
+
readonly version: 1;
|
|
25
|
+
readonly sessionId: string;
|
|
26
|
+
/** When the selector opened the sandbox, as an ISO timestamp. */
|
|
27
|
+
readonly openedAt: string;
|
|
28
|
+
/** The toolset version the environment's snapshot was prepared with. */
|
|
29
|
+
readonly toolset: string;
|
|
30
|
+
readonly network: "deny-all";
|
|
31
|
+
}
|
|
32
|
+
/** What the session remembers about its sandbox, so cleanup never opens one just to delete it. */
|
|
33
|
+
export interface SandboxRecord {
|
|
34
|
+
readonly openedAt: string;
|
|
35
|
+
/** The turn whose first file-tool call last passed the gate. */
|
|
36
|
+
readonly checkedTurn?: string;
|
|
37
|
+
}
|
|
38
|
+
export declare const sandboxRecord: StateHandle<SandboxRecord | null>;
|
|
39
|
+
/** The session's sandbox did not come from `@segmentstream/agent/sandbox`; the tools fail closed. */
|
|
40
|
+
export declare class SandboxUnavailableError extends Error {
|
|
41
|
+
readonly name = "SandboxUnavailableError";
|
|
42
|
+
}
|
|
43
|
+
/** The part of an eve tool or hook context the sandbox functions use. */
|
|
44
|
+
export interface SandboxContext {
|
|
45
|
+
readonly session: {
|
|
46
|
+
readonly id: string;
|
|
47
|
+
readonly turn: {
|
|
48
|
+
readonly id: string;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
getSandbox(): Promise<RuntimeSandboxSession>;
|
|
52
|
+
}
|
|
53
|
+
/** Parses a marker file; `undefined` for anything the SDK's selector would not have written. */
|
|
54
|
+
export declare function parseSandboxMarker(bytes: Uint8Array | null): SandboxMarker | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* Initializes a sandbox the SDK's selector just opened: writes the marker and records the opening
|
|
57
|
+
* in session state. Outside an eve step there is no session state; the marker still gates the
|
|
58
|
+
* tools and the age check still runs on first use.
|
|
59
|
+
*/
|
|
60
|
+
export declare function initializeSandbox(sandbox: SandboxSession, input: {
|
|
61
|
+
readonly sessionId: string;
|
|
62
|
+
readonly toolset: string;
|
|
63
|
+
readonly now?: Date;
|
|
64
|
+
readonly record?: StateHandle<SandboxRecord | null>;
|
|
65
|
+
}): Promise<SandboxMarker>;
|
|
66
|
+
/**
|
|
67
|
+
* The session's sandbox, once it has passed the gate: it must carry the SDK's marker for this
|
|
68
|
+
* session, so the file tools never run in eve's default sandbox, whose network is open. The first
|
|
69
|
+
* call in each turn checks the marker; a sandbox older than a day is deleted through its handle
|
|
70
|
+
* and reopened, carrying over the attachments staged within the last day so that the files of the
|
|
71
|
+
* current turn stay where eve's references point.
|
|
72
|
+
*/
|
|
73
|
+
export declare function sessionSandbox(context: SandboxContext, options?: {
|
|
74
|
+
readonly now?: number;
|
|
75
|
+
readonly record?: StateHandle<SandboxRecord | null>;
|
|
76
|
+
}): Promise<RuntimeSandboxSession>;
|
|
77
|
+
/** A file from the sandbox, as the platform stores it. */
|
|
78
|
+
export interface SandboxFile {
|
|
79
|
+
/** Its real path, with symbolic links resolved. */
|
|
80
|
+
readonly path: string;
|
|
81
|
+
readonly filename: string;
|
|
82
|
+
readonly content: Uint8Array;
|
|
83
|
+
}
|
|
84
|
+
/** The named path is not a file the platform takes from the sandbox; the message says why. */
|
|
85
|
+
export declare class SandboxFileRefused extends TypeError {
|
|
86
|
+
}
|
|
87
|
+
/** The named file is larger than the caller allows. */
|
|
88
|
+
export declare class SandboxFileTooLarge extends RangeError {
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Reads a file the model names in the session's sandbox, after the gate. Its real path must stay
|
|
92
|
+
* inside `/workspace`, so a symbolic link cannot hand the platform a file from elsewhere in the
|
|
93
|
+
* machine, and it must be a non-empty regular file of at most `maximumBytes`.
|
|
94
|
+
*/
|
|
95
|
+
export declare function sandboxFile(context: SandboxContext, path: string, options: {
|
|
96
|
+
readonly maximumBytes: number;
|
|
97
|
+
readonly now?: number;
|
|
98
|
+
readonly record?: StateHandle<SandboxRecord | null>;
|
|
99
|
+
}): Promise<SandboxFile>;
|
|
100
|
+
/**
|
|
101
|
+
* Deletes the session's sandbox when the session recorded one, such as when its history is
|
|
102
|
+
* cleared. Without a record it does nothing: asking eve for the sandbox would open one.
|
|
103
|
+
*/
|
|
104
|
+
export declare function releaseSandbox(context: Pick<SandboxContext, "getSandbox">, record?: StateHandle<SandboxRecord | null>): Promise<boolean>;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { fileURLToPath as __eveFileURLToPath } from "node:url";
|
|
2
|
+
import { dirname as __eveDirname } from "node:path";
|
|
3
|
+
import { createRequire as __eveCreateRequire } from "node:module";
|
|
4
|
+
const __filename = __eveFileURLToPath(import.meta.url);
|
|
5
|
+
__eveDirname(__filename);
|
|
6
|
+
__eveCreateRequire(import.meta.url);
|
|
7
|
+
import { posix } from "node:path";
|
|
8
|
+
import { defineState } from "eve/context";
|
|
9
|
+
const SANDBOX_MARKER_PATH = "/workspace/.segmentstream/sandbox.json";
|
|
10
|
+
const SANDBOX_ATTACHMENTS_DIRECTORY = "/workspace/.eve/attachments";
|
|
11
|
+
const SANDBOX_MAXIMUM_AGE_MS = 864e5;
|
|
12
|
+
const CARRIED_ATTACHMENTS = {
|
|
13
|
+
files: 32,
|
|
14
|
+
bytes: 67108864
|
|
15
|
+
};
|
|
16
|
+
const STAGED_PATH = /^\/workspace\/\.eve\/attachments\/[0-9a-f]{16}\/[\w.-]+$/;
|
|
17
|
+
const sandboxRecord = defineState("segmentstream.sandbox", () => null);
|
|
18
|
+
var SandboxUnavailableError = class extends Error {
|
|
19
|
+
name = "SandboxUnavailableError";
|
|
20
|
+
};
|
|
21
|
+
function isRecord(value) {
|
|
22
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
23
|
+
}
|
|
24
|
+
function parseSandboxMarker(bytes) {
|
|
25
|
+
if (bytes === null) return;
|
|
26
|
+
let value;
|
|
27
|
+
try {
|
|
28
|
+
value = JSON.parse(Buffer.from(bytes).toString("utf8"));
|
|
29
|
+
} catch {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
return isRecord(value) && value.version === 1 && typeof value.sessionId === "string" && value.sessionId !== "" && typeof value.openedAt === "string" && !Number.isNaN(Date.parse(value.openedAt)) && typeof value.toolset === "string" && value.toolset !== "" && value.network === "deny-all" ? {
|
|
33
|
+
version: 1,
|
|
34
|
+
sessionId: value.sessionId,
|
|
35
|
+
openedAt: value.openedAt,
|
|
36
|
+
toolset: value.toolset,
|
|
37
|
+
network: "deny-all"
|
|
38
|
+
} : void 0;
|
|
39
|
+
}
|
|
40
|
+
async function initializeSandbox(sandbox, input) {
|
|
41
|
+
const marker = {
|
|
42
|
+
version: 1,
|
|
43
|
+
sessionId: input.sessionId,
|
|
44
|
+
openedAt: (input.now ?? new Date()).toISOString(),
|
|
45
|
+
toolset: input.toolset,
|
|
46
|
+
network: "deny-all"
|
|
47
|
+
};
|
|
48
|
+
await sandbox.writeTextFile({
|
|
49
|
+
path: SANDBOX_MARKER_PATH,
|
|
50
|
+
content: `${JSON.stringify(marker)}\n`
|
|
51
|
+
});
|
|
52
|
+
try {
|
|
53
|
+
(input.record ?? sandboxRecord).update(() => ({ openedAt: marker.openedAt }));
|
|
54
|
+
} catch {}
|
|
55
|
+
return marker;
|
|
56
|
+
}
|
|
57
|
+
async function verifiedMarker(sandbox, sessionId) {
|
|
58
|
+
const marker = parseSandboxMarker(await sandbox.readBinaryFile({ path: SANDBOX_MARKER_PATH }));
|
|
59
|
+
if (marker === void 0 || marker.sessionId !== sessionId) throw new SandboxUnavailableError("The sandbox tools are unavailable: this session's sandbox was not opened by @segmentstream/agent/sandbox. agent/agent/sandbox.ts must re-export it.");
|
|
60
|
+
return marker;
|
|
61
|
+
}
|
|
62
|
+
async function recentAttachments(sandbox, since) {
|
|
63
|
+
const listed = await sandbox.run({ command: `find ${SANDBOX_ATTACHMENTS_DIRECTORY} -type f -newermt @${Math.floor(since / 1e3)} -printf '%T@\\t%s\\t%p\\n' 2>/dev/null | sort -rn` });
|
|
64
|
+
const files = [];
|
|
65
|
+
let bytes = 0;
|
|
66
|
+
for (const line of listed.stdout.split("\n")) {
|
|
67
|
+
const [, size = "", path = ""] = line.split(" ");
|
|
68
|
+
if (!STAGED_PATH.test(path) || files.length >= CARRIED_ATTACHMENTS.files || bytes + Number(size) > CARRIED_ATTACHMENTS.bytes) continue;
|
|
69
|
+
const content = await sandbox.readBinaryFile({ path });
|
|
70
|
+
if (content !== null) {
|
|
71
|
+
files.push({
|
|
72
|
+
path,
|
|
73
|
+
content
|
|
74
|
+
});
|
|
75
|
+
bytes += content.byteLength;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return files;
|
|
79
|
+
}
|
|
80
|
+
async function sessionSandbox(context, options = {}) {
|
|
81
|
+
const record = options.record ?? sandboxRecord;
|
|
82
|
+
const now = options.now ?? Date.now();
|
|
83
|
+
const turn = context.session.turn.id;
|
|
84
|
+
const sandbox = await context.getSandbox();
|
|
85
|
+
if (record.get()?.checkedTurn === turn) return sandbox;
|
|
86
|
+
const marker = await verifiedMarker(sandbox, context.session.id);
|
|
87
|
+
if (now - Date.parse(marker.openedAt) < 864e5) {
|
|
88
|
+
record.update(() => ({
|
|
89
|
+
openedAt: marker.openedAt,
|
|
90
|
+
checkedTurn: turn
|
|
91
|
+
}));
|
|
92
|
+
return sandbox;
|
|
93
|
+
}
|
|
94
|
+
const carried = await recentAttachments(sandbox, now - SANDBOX_MAXIMUM_AGE_MS);
|
|
95
|
+
await sandbox.delete();
|
|
96
|
+
record.update(() => null);
|
|
97
|
+
const fresh = await context.getSandbox();
|
|
98
|
+
const reopened = await verifiedMarker(fresh, context.session.id);
|
|
99
|
+
for (const file of carried) await fresh.writeBinaryFile(file);
|
|
100
|
+
record.update(() => ({
|
|
101
|
+
openedAt: reopened.openedAt,
|
|
102
|
+
checkedTurn: turn
|
|
103
|
+
}));
|
|
104
|
+
return fresh;
|
|
105
|
+
}
|
|
106
|
+
var SandboxFileRefused = class extends TypeError {};
|
|
107
|
+
var SandboxFileTooLarge = class extends RangeError {};
|
|
108
|
+
function shellQuoted(value) {
|
|
109
|
+
return `'${value.replaceAll("'", `'\\''`)}'`;
|
|
110
|
+
}
|
|
111
|
+
async function sandboxFile(context, path, options) {
|
|
112
|
+
if (!path.startsWith("/workspace/") || /[\0\r\n]/.test(path)) throw new SandboxFileRefused("path must be the absolute path of a file inside /workspace.");
|
|
113
|
+
const sandbox = await sessionSandbox(context, options);
|
|
114
|
+
const described = await sandbox.run({ command: `real="$(realpath -e -- ${shellQuoted(path)})" && printf '%s\\n' "$real" && stat -c '%F|%s' -- "$real"` });
|
|
115
|
+
const [real = "", stat = ""] = described.stdout.split("\n");
|
|
116
|
+
const [kind, size] = stat.split("|");
|
|
117
|
+
if (described.exitCode !== 0 || !real.startsWith("/workspace/") || posix.normalize(real) !== real) throw new SandboxFileRefused(`${path} names no file inside /workspace.`);
|
|
118
|
+
if (kind === "regular empty file") throw new SandboxFileRefused(`${path} is empty.`);
|
|
119
|
+
if (kind !== "regular file") throw new SandboxFileRefused(`${path} is not a regular file.`);
|
|
120
|
+
const tooLarge = () => new SandboxFileTooLarge(`${path} is larger than the ${options.maximumBytes / 1024 / 1024} MB an attachment may be.`);
|
|
121
|
+
if (Number(size) > options.maximumBytes) throw tooLarge();
|
|
122
|
+
const content = await sandbox.readBinaryFile({ path: real });
|
|
123
|
+
const filename = posix.basename(real);
|
|
124
|
+
if (content === null || content.byteLength === 0) throw new SandboxFileRefused(`${path} could not be read.`);
|
|
125
|
+
if (content.byteLength > options.maximumBytes) throw tooLarge();
|
|
126
|
+
if (filename.length > 255 || /[\x00-\x1f\x7f\\]/.test(filename)) throw new SandboxFileRefused(`${filename} cannot be stored as a file name; rename the file.`);
|
|
127
|
+
return {
|
|
128
|
+
path: real,
|
|
129
|
+
filename,
|
|
130
|
+
content
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
async function releaseSandbox(context, record = sandboxRecord) {
|
|
134
|
+
if (record.get() === null) return false;
|
|
135
|
+
try {
|
|
136
|
+
await (await context.getSandbox()).delete();
|
|
137
|
+
} finally {
|
|
138
|
+
record.update(() => null);
|
|
139
|
+
}
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
export { CARRIED_ATTACHMENTS, SANDBOX_ATTACHMENTS_DIRECTORY, SANDBOX_MARKER_PATH, SANDBOX_MAXIMUM_AGE_MS, SandboxFileRefused, SandboxFileTooLarge, SandboxUnavailableError, initializeSandbox, parseSandboxMarker, releaseSandbox, sandboxFile, sandboxRecord, sessionSandbox };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type SandboxSession } from "eve/sandbox";
|
|
2
|
+
/**
|
|
3
|
+
* `@segmentstream/agent/sandbox`: the isolated sandbox of a workspace agent (`docs/agent.md`,
|
|
4
|
+
* "Sandbox"). eve takes a sandbox only from the agent itself, never from an extension, so the
|
|
5
|
+
* agent's `agent/agent/sandbox.ts` re-exports this module:
|
|
6
|
+
*
|
|
7
|
+
* export { default, environment } from "@segmentstream/agent/sandbox";
|
|
8
|
+
*
|
|
9
|
+
* It replaces eve's default sandbox, which on Vercel opens with an unrestricted network, with a
|
|
10
|
+
* Vercel Sandbox that has no network at all. The model reaches it through the extension's file
|
|
11
|
+
* tools; company data stays behind the ontology tools in the app runtime, so no credential, token
|
|
12
|
+
* or MCP connection ever enters it.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* The file toolset, installed once into the environment's snapshot while the network is still
|
|
16
|
+
* open. The Ubuntu packages exist in Ubuntu 26.04, the eve base image's release; a missing name
|
|
17
|
+
* would fail preparation and so the deploy. pdfplumber, which Ubuntu does not ship, is pinned with
|
|
18
|
+
* the two dependencies Ubuntu's are too old for, by version and hash, into the sandbox user's own
|
|
19
|
+
* site-packages; its pdfminer.six 20260107 is Ubuntu's python3-pdfminer.
|
|
20
|
+
*/
|
|
21
|
+
export declare const SANDBOX_PREPARE_SCRIPT = "set -euo pipefail\nsudo apt-get update -q\nsudo env DEBIAN_FRONTEND=noninteractive apt-get install -y -q --no-install-recommends \\\n poppler-utils qpdf tesseract-ocr tesseract-ocr-eng tesseract-ocr-rus 7zip unar \\\n imagemagick ghostscript python3-pandas python3-openpyxl python3-pypdf python3-pdfminer\nsudo apt-get clean\nsudo rm -rf /var/lib/apt/lists/*\nrequirements=\"$(mktemp)\"\ncat > \"$requirements\" <<'REQUIREMENTS'\npdfplumber==0.11.10 \\\n --hash=sha256:7741ea81bf165b474b153e6789d10d18e06b6ddcf3ec84289c3ef2fed6802580\npillow==12.2.0 \\\n --hash=sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286 \\\n --hash=sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be \\\n --hash=sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f \\\n --hash=sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601\npypdfium2==5.9.0 \\\n --hash=sha256:7ce3a3dd23ec0adaa079d8be54565ba2aa2f6060e76a4989cd42dabc163d74ee \\\n --hash=sha256:1d902e03dff5efd51d93cd23d3e55bde53802fa6207bcd0e455239518859a069\nREQUIREMENTS\npython3 -m pip install --user --break-system-packages --no-cache-dir --no-deps \\\n --only-binary=:all: --require-hashes --disable-pip-version-check -r \"$requirements\"\nrm -f \"$requirements\"\nmkdir -p /workspace/work\nfor tool in pdftotext pdftoppm qpdf tesseract gs 7z unar unzip jq rg; do\n command -v \"$tool\" > /dev/null\ndone\nlanguages=\"$(tesseract --list-langs)\"\ngrep -qx eng <<< \"$languages\"\ngrep -qx rus <<< \"$languages\"\npython3 -c 'import openpyxl, pandas, pdfminer, pdfplumber, pypdf, pypdfium2'\n";
|
|
22
|
+
/**
|
|
23
|
+
* Identifies the toolset. eve keys a prepared snapshot on the authored `sandbox.ts` and the
|
|
24
|
+
* environment's options, not on a `prepare` imported from a package, so the version travels as a
|
|
25
|
+
* sandbox tag: a changed toolset is a new snapshot.
|
|
26
|
+
*/
|
|
27
|
+
export declare const SANDBOX_TOOLSET_VERSION: string;
|
|
28
|
+
/** Each live sandbox's compute; Vercel pairs every vCPU with 2 GB of memory. */
|
|
29
|
+
export declare const SANDBOX_VCPUS = 2;
|
|
30
|
+
export declare function prepareSandbox(sandbox: SandboxSession): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* The environment `eve build` prepares. It carries no network policy: one applied here would hold
|
|
33
|
+
* during preparation too, before the toolset is installed.
|
|
34
|
+
*/
|
|
35
|
+
export declare const environment: import("eve/sandbox").SandboxEnvironment<import("eve/sandbox/vercel").VercelSandboxRuntimeOptions, import("eve/sandbox/vercel").VercelSandboxSession>;
|
|
36
|
+
/** Opens the session's sandbox without network access and marks it as the SDK's. */
|
|
37
|
+
declare const _default: import("eve/sandbox").SandboxSelector;
|
|
38
|
+
export default _default;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { fileURLToPath as __eveFileURLToPath } from "node:url";
|
|
2
|
+
import { dirname as __eveDirname } from "node:path";
|
|
3
|
+
import { createRequire as __eveCreateRequire } from "node:module";
|
|
4
|
+
const __filename = __eveFileURLToPath(import.meta.url);
|
|
5
|
+
__eveDirname(__filename);
|
|
6
|
+
__eveCreateRequire(import.meta.url);
|
|
7
|
+
import { initializeSandbox } from "./sandbox-session.mjs";
|
|
8
|
+
import { createHash } from "node:crypto";
|
|
9
|
+
import { defineSandbox } from "eve/sandbox";
|
|
10
|
+
import { VercelSandbox } from "eve/sandbox/vercel";
|
|
11
|
+
const SANDBOX_PREPARE_SCRIPT = `set -euo pipefail
|
|
12
|
+
sudo apt-get update -q
|
|
13
|
+
sudo env DEBIAN_FRONTEND=noninteractive apt-get install -y -q --no-install-recommends \\
|
|
14
|
+
poppler-utils qpdf tesseract-ocr tesseract-ocr-eng tesseract-ocr-rus 7zip unar \\
|
|
15
|
+
imagemagick ghostscript python3-pandas python3-openpyxl python3-pypdf python3-pdfminer
|
|
16
|
+
sudo apt-get clean
|
|
17
|
+
sudo rm -rf /var/lib/apt/lists/*
|
|
18
|
+
requirements="$(mktemp)"
|
|
19
|
+
cat > "$requirements" <<'REQUIREMENTS'
|
|
20
|
+
pdfplumber==0.11.10 \\
|
|
21
|
+
--hash=sha256:7741ea81bf165b474b153e6789d10d18e06b6ddcf3ec84289c3ef2fed6802580
|
|
22
|
+
pillow==12.2.0 \\
|
|
23
|
+
--hash=sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286 \\
|
|
24
|
+
--hash=sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be \\
|
|
25
|
+
--hash=sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f \\
|
|
26
|
+
--hash=sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601
|
|
27
|
+
pypdfium2==5.9.0 \\
|
|
28
|
+
--hash=sha256:7ce3a3dd23ec0adaa079d8be54565ba2aa2f6060e76a4989cd42dabc163d74ee \\
|
|
29
|
+
--hash=sha256:1d902e03dff5efd51d93cd23d3e55bde53802fa6207bcd0e455239518859a069
|
|
30
|
+
REQUIREMENTS
|
|
31
|
+
python3 -m pip install --user --break-system-packages --no-cache-dir --no-deps \\
|
|
32
|
+
--only-binary=:all: --require-hashes --disable-pip-version-check -r "$requirements"
|
|
33
|
+
rm -f "$requirements"
|
|
34
|
+
mkdir -p /workspace/work
|
|
35
|
+
for tool in pdftotext pdftoppm qpdf tesseract gs 7z unar unzip jq rg; do
|
|
36
|
+
command -v "$tool" > /dev/null
|
|
37
|
+
done
|
|
38
|
+
languages="$(tesseract --list-langs)"
|
|
39
|
+
grep -qx eng <<< "$languages"
|
|
40
|
+
grep -qx rus <<< "$languages"
|
|
41
|
+
python3 -c 'import openpyxl, pandas, pdfminer, pdfplumber, pypdf, pypdfium2'
|
|
42
|
+
`;
|
|
43
|
+
const SANDBOX_TOOLSET_VERSION = createHash("sha256").update(SANDBOX_PREPARE_SCRIPT).digest("hex").slice(0, 16);
|
|
44
|
+
const SANDBOX_VCPUS = 2;
|
|
45
|
+
async function prepareSandbox(sandbox) {
|
|
46
|
+
const result = await sandbox.run({ command: SANDBOX_PREPARE_SCRIPT });
|
|
47
|
+
if (result.exitCode !== 0) throw new Error(`The SegmentStream sandbox toolset failed to install (exit ${result.exitCode}): ` + result.stderr.slice(-4e3));
|
|
48
|
+
}
|
|
49
|
+
const environment = VercelSandbox.environment({
|
|
50
|
+
prepare: prepareSandbox,
|
|
51
|
+
tags: { segmentstreamToolset: SANDBOX_TOOLSET_VERSION }
|
|
52
|
+
});
|
|
53
|
+
var sandbox_default = defineSandbox(async ({ session }) => {
|
|
54
|
+
const sandbox = await environment.open({
|
|
55
|
+
networkPolicy: "deny-all",
|
|
56
|
+
resources: { vcpus: 2 }
|
|
57
|
+
});
|
|
58
|
+
await initializeSandbox(sandbox, {
|
|
59
|
+
sessionId: session.id,
|
|
60
|
+
toolset: SANDBOX_TOOLSET_VERSION
|
|
61
|
+
});
|
|
62
|
+
return sandbox;
|
|
63
|
+
});
|
|
64
|
+
export { SANDBOX_PREPARE_SCRIPT, SANDBOX_TOOLSET_VERSION, SANDBOX_VCPUS, sandbox_default as default, environment, prepareSandbox };
|
|
@@ -20,7 +20,7 @@ import { archivedRoutingMessages } from "../group-routing/routing-context.mjs";
|
|
|
20
20
|
import { dispatchStructuredMessage, structuredMessage } from "./structured.mjs";
|
|
21
21
|
import { scopedAddress, sessionPolicy } from "./sessions.mjs";
|
|
22
22
|
import { handleWorkspaceCallback, isWorkspaceCallback } from "./callbacks.mjs";
|
|
23
|
-
import { t as agentBuild } from "../../../_chunks/build-metadata-
|
|
23
|
+
import { t as agentBuild } from "../../../_chunks/build-metadata-DR4gACLZ.mjs";
|
|
24
24
|
import { attachmentLifecycle } from "./lifecycle.mjs";
|
|
25
25
|
import { observedMessages } from "./observed.mjs";
|
|
26
26
|
import { expireDeferredCards, handleDeferredCallback, isDeferredCallback } from "./deferred-confirmations.mjs";
|
|
@@ -29,7 +29,7 @@ import { gatewayAttachmentDescriber } from "./attachment-description.mjs";
|
|
|
29
29
|
import { MAXIMUM_DOWNLOAD_BYTES, connectionProxy } from "../connection-proxy.mjs";
|
|
30
30
|
import { agentAttachments, parseAttachmentUrl } from "../attachments.mjs";
|
|
31
31
|
import { agentIdentities } from "../identities.mjs";
|
|
32
|
-
import { replyDelivery } from "./silent.mjs";
|
|
32
|
+
import { interimTexts, replyDelivery } from "./silent.mjs";
|
|
33
33
|
import { handleConfirmationCallback, telegramConfirmationEvents } from "./confirmations.mjs";
|
|
34
34
|
import { captureRouteDecider, captureTelegramFetch, conversationIdentities, recordGateDecision, telegramConversationCapture } from "./conversations.mjs";
|
|
35
35
|
import { runtimeTokenVerifier } from "../runtime-identity.mjs";
|
|
@@ -88,6 +88,7 @@ function segmentstreamTelegram(options) {
|
|
|
88
88
|
});
|
|
89
89
|
const bot = botIdentityCache();
|
|
90
90
|
const proxies = new Map();
|
|
91
|
+
const interimDelivered = interimTexts();
|
|
91
92
|
let identities;
|
|
92
93
|
let verify = options.verify;
|
|
93
94
|
function settings() {
|
|
@@ -372,7 +373,10 @@ function segmentstreamTelegram(options) {
|
|
|
372
373
|
}
|
|
373
374
|
},
|
|
374
375
|
events: {
|
|
375
|
-
...telegramFailureEvents
|
|
376
|
+
...telegramFailureEvents({
|
|
377
|
+
archive,
|
|
378
|
+
identities: pairing
|
|
379
|
+
}),
|
|
376
380
|
...telegramConfirmationEvents(archive, observed, {
|
|
377
381
|
environment,
|
|
378
382
|
token,
|
|
@@ -415,7 +419,10 @@ function segmentstreamTelegram(options) {
|
|
|
415
419
|
await channel.telegram.post(failureText(reference));
|
|
416
420
|
return;
|
|
417
421
|
}
|
|
418
|
-
await replyDelivery(observed, () => telegramSendScope(environment, settings().connection)
|
|
422
|
+
await replyDelivery(observed, () => telegramSendScope(environment, settings().connection), {
|
|
423
|
+
enabled: () => settings().interimMessages === true,
|
|
424
|
+
delivered: interimDelivered
|
|
425
|
+
})(event, channel, context);
|
|
419
426
|
}
|
|
420
427
|
}
|
|
421
428
|
});
|
|
@@ -28,6 +28,22 @@ export declare function recordInboundContent(content: {
|
|
|
28
28
|
readonly media?: string;
|
|
29
29
|
readonly decoded?: ReadonlyArray<DecodedAttachment>;
|
|
30
30
|
}): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Records eve's unrecoverable session failure from the channel. The channel receives it even when
|
|
33
|
+
* eve publishes it after its workflow loop throws, which runs no hooks. Never throws, so the
|
|
34
|
+
* member's notice never depends on the archive.
|
|
35
|
+
*/
|
|
36
|
+
export declare function recordSessionFailure(input: {
|
|
37
|
+
readonly archive: Conversations;
|
|
38
|
+
readonly identities: () => AgentIdentities;
|
|
39
|
+
readonly chatId: string | null;
|
|
40
|
+
readonly threadId: string | null;
|
|
41
|
+
readonly failure: {
|
|
42
|
+
readonly code: string;
|
|
43
|
+
readonly message: string;
|
|
44
|
+
readonly sessionId: string;
|
|
45
|
+
};
|
|
46
|
+
}): Promise<void>;
|
|
31
47
|
/** Captures message sends and button/status edits, including pairing and proactive replies. */
|
|
32
48
|
export declare function captureTelegramFetch(fetchImpl: typeof fetch, archive: Conversations, identities: () => AgentIdentities): typeof fetch;
|
|
33
49
|
/** Reuses the request's verified lookup; it never survives into another inbound request. */
|
|
@@ -5,8 +5,9 @@ const __filename = __eveFileURLToPath(import.meta.url);
|
|
|
5
5
|
__eveDirname(__filename);
|
|
6
6
|
__eveCreateRequire(import.meta.url);
|
|
7
7
|
import { n as currentInboundRequest, p as reportTelegramError, s as currentThreadId } from "../../../_chunks/inbound-request-RIRNBxUC.mjs";
|
|
8
|
+
import { n as contentText, t as CONTENT_CAPS } from "../../../_chunks/content-CvFsrmSD.mjs";
|
|
8
9
|
import { currentConfirmationPrompt, observedChatKey } from "../group-routing/observed.mjs";
|
|
9
|
-
import { conversationEvent } from "../conversations.mjs";
|
|
10
|
+
import { conversationEvent, sessionFailureEventId } from "../conversations.mjs";
|
|
10
11
|
import { archivedRoutingMessages } from "../group-routing/routing-context.mjs";
|
|
11
12
|
import { contentShape, messageContent } from "./content.mjs";
|
|
12
13
|
import { messageMetadata } from "./observed.mjs";
|
|
@@ -517,6 +518,62 @@ async function recordInboundContent(content) {
|
|
|
517
518
|
await capture.archive.append(event);
|
|
518
519
|
capture.event = event;
|
|
519
520
|
}
|
|
521
|
+
async function outboundAudience(chatId, identities) {
|
|
522
|
+
const inbound = currentInboundRequest();
|
|
523
|
+
const capture = inbound === void 0 ? void 0 : captures.get(inbound);
|
|
524
|
+
let participantIds = capture?.event.participantIds ?? [];
|
|
525
|
+
const chatType = capture?.event.chatType ?? (chatId.startsWith("-") ? "group" : "private");
|
|
526
|
+
if (chatType === "private" && participantIds.length === 0 && capture === void 0) try {
|
|
527
|
+
const lookup = await identities().lookup({
|
|
528
|
+
provider: "telegram",
|
|
529
|
+
externalId: chatId
|
|
530
|
+
});
|
|
531
|
+
if (lookup.status === "paired") participantIds = [lookup.member.id];
|
|
532
|
+
} catch (error) {
|
|
533
|
+
console.error("[segmentstream] outbound participant lookup failed", { error });
|
|
534
|
+
}
|
|
535
|
+
return {
|
|
536
|
+
capture,
|
|
537
|
+
chatType,
|
|
538
|
+
participantIds
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
async function recordSessionFailure(input) {
|
|
542
|
+
const { failure } = input;
|
|
543
|
+
const sessionId = failure.sessionId === "" ? null : failure.sessionId;
|
|
544
|
+
try {
|
|
545
|
+
const errorText = contentText(failure.message, CONTENT_CAPS.error);
|
|
546
|
+
if (input.chatId === null) {
|
|
547
|
+
console.error("[segmentstream] agent log dropped unaddressed failure", {
|
|
548
|
+
sessionId,
|
|
549
|
+
eventType: "session.failed",
|
|
550
|
+
code: failure.code,
|
|
551
|
+
message: errorText
|
|
552
|
+
});
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
const { chatType, participantIds } = await outboundAudience(input.chatId, input.identities);
|
|
556
|
+
await input.archive.append(conversationEvent({
|
|
557
|
+
eventId: sessionId === null ? `eve:session.failed:${randomUUID()}` : sessionFailureEventId(sessionId),
|
|
558
|
+
provider: "telegram",
|
|
559
|
+
chatId: input.chatId,
|
|
560
|
+
chatType,
|
|
561
|
+
threadId: input.threadId,
|
|
562
|
+
participantIds,
|
|
563
|
+
sessionId,
|
|
564
|
+
kind: "decision",
|
|
565
|
+
decision: "silent",
|
|
566
|
+
reason: "session.failed",
|
|
567
|
+
status: "failed",
|
|
568
|
+
errorText
|
|
569
|
+
}));
|
|
570
|
+
} catch (error) {
|
|
571
|
+
console.error("[segmentstream] recording session failure failed", {
|
|
572
|
+
sessionId,
|
|
573
|
+
error
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
}
|
|
520
577
|
function captureTelegramFetch(fetchImpl, archive, identities) {
|
|
521
578
|
return async (input, init) => {
|
|
522
579
|
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
@@ -525,19 +582,7 @@ function captureTelegramFetch(fetchImpl, archive, identities) {
|
|
|
525
582
|
const body = record(JSON.parse(init.body));
|
|
526
583
|
const chatId = id(body.chat_id);
|
|
527
584
|
if (chatId === null) return fetchImpl(input, init);
|
|
528
|
-
const
|
|
529
|
-
const capture = inbound === void 0 ? void 0 : captures.get(inbound);
|
|
530
|
-
let participantIds = capture?.event.participantIds ?? [];
|
|
531
|
-
const chatType = capture?.event.chatType ?? (chatId.startsWith("-") ? "group" : "private");
|
|
532
|
-
if (chatType === "private" && participantIds.length === 0 && capture === void 0) try {
|
|
533
|
-
const lookup = await identities().lookup({
|
|
534
|
-
provider: "telegram",
|
|
535
|
-
externalId: chatId
|
|
536
|
-
});
|
|
537
|
-
if (lookup.status === "paired") participantIds = [lookup.member.id];
|
|
538
|
-
} catch (error) {
|
|
539
|
-
console.error("[segmentstream] outbound participant lookup failed", { error });
|
|
540
|
-
}
|
|
585
|
+
const { capture, chatType, participantIds } = await outboundAudience(chatId, identities);
|
|
541
586
|
const event = conversationEvent({
|
|
542
587
|
eventId: `telegram:${edit ? "edit" : "send"}:${randomUUID()}`,
|
|
543
588
|
provider: "telegram",
|
|
@@ -681,4 +726,4 @@ function telegramConversationCapture(input) {
|
|
|
681
726
|
}
|
|
682
727
|
};
|
|
683
728
|
}
|
|
684
|
-
export { REVISION_CLAIM_TTL_MS, captureRouteDecider, captureTelegramFetch, captureTelegramWebhook, conversationIdentities, recordGateDecision, recordInboundContent, recordRouteDecision, telegramConversationCapture };
|
|
729
|
+
export { REVISION_CLAIM_TTL_MS, captureRouteDecider, captureTelegramFetch, captureTelegramWebhook, conversationIdentities, recordGateDecision, recordInboundContent, recordRouteDecision, recordSessionFailure, telegramConversationCapture };
|
|
@@ -4,7 +4,7 @@ import { createRequire as __eveCreateRequire } from "node:module";
|
|
|
4
4
|
const __filename = __eveFileURLToPath(import.meta.url);
|
|
5
5
|
__eveDirname(__filename);
|
|
6
6
|
__eveCreateRequire(import.meta.url);
|
|
7
|
-
import { t as agentBuild } from "../../../_chunks/build-metadata-
|
|
7
|
+
import { t as agentBuild } from "../../../_chunks/build-metadata-DR4gACLZ.mjs";
|
|
8
8
|
import { runtimeRequestFailure } from "./runtime-request.mjs";
|
|
9
9
|
import { GET } from "eve/channels";
|
|
10
10
|
const HEALTH_ROUTE = "/eve/v1/telegram/health";
|
|
@@ -9,7 +9,7 @@ import { object } from "../confirmation-description.mjs";
|
|
|
9
9
|
import { n as currentInboundRequest, p as reportTelegramError } from "../../../_chunks/inbound-request-RIRNBxUC.mjs";
|
|
10
10
|
import { needsConfirmation } from "../confirmations.mjs";
|
|
11
11
|
import { OntologyActionNotPermitted, actionToolName, ontologyTools } from "../ontology-tools.mjs";
|
|
12
|
-
import { t as agentBuild } from "../../../_chunks/build-metadata-
|
|
12
|
+
import { t as agentBuild } from "../../../_chunks/build-metadata-DR4gACLZ.mjs";
|
|
13
13
|
var AttachmentLifecycleNotPermitted = class extends Error {
|
|
14
14
|
code = "attachment_lifecycle_not_permitted";
|
|
15
15
|
};
|
|
@@ -4,10 +4,32 @@ export declare function isSilentReply(text: string): boolean;
|
|
|
4
4
|
/** Use the same classification for delivery and its archive decision. Never strip real prose. */
|
|
5
5
|
export declare function completionKind(message: string | null | undefined, finishReason?: string): "reply" | "tool_calls" | "empty" | "whitespace" | "silent_marker";
|
|
6
6
|
type MessageCompleted = NonNullable<TelegramChannelEvents["message.completed"]>;
|
|
7
|
+
/** The texts one runtime delivered as interim messages, per session turn. */
|
|
8
|
+
export interface InterimTexts {
|
|
9
|
+
has(turn: string, text: string): boolean;
|
|
10
|
+
add(turn: string, text: string): void;
|
|
11
|
+
}
|
|
12
|
+
/** How many recent turns {@link interimTexts} remembers. */
|
|
13
|
+
export declare const INTERIM_TURNS = 256;
|
|
14
|
+
/** Remembers the interim texts of the most recent turns, the oldest turn forgotten first. */
|
|
15
|
+
export declare function interimTexts(limit?: number): InterimTexts;
|
|
16
|
+
/**
|
|
17
|
+
* `channels.telegram.interimMessages`: the text the model writes in a step that ends in tool
|
|
18
|
+
* calls, such as a word on what a long job is doing, reaches the chat as its own message.
|
|
19
|
+
*/
|
|
20
|
+
export interface InterimMessages {
|
|
21
|
+
readonly enabled: () => boolean;
|
|
22
|
+
/** What this runtime delivered, so neither an interim text nor a final reply goes out twice. */
|
|
23
|
+
readonly delivered: InterimTexts;
|
|
24
|
+
}
|
|
7
25
|
/**
|
|
8
26
|
* The channel's final reply: formatted and split before Eve's plain-text splitter, with one
|
|
9
|
-
* queue slot for the whole reply.
|
|
27
|
+
* queue slot for the whole reply. Explicit silence markers have no delivery. Tool-call steps have
|
|
28
|
+
* none either unless `interim` is enabled: then their text is delivered exactly like a reply, but
|
|
29
|
+
* not remembered for group routing, which counts only final replies, as its recovery from the
|
|
30
|
+
* session stream does. A text already delivered as an interim message in the same turn, whether
|
|
31
|
+
* a repeat or the final reply, is not delivered again.
|
|
10
32
|
*/
|
|
11
|
-
export declare function replyDelivery(observed?: ObservedMessages, scope?: () => string): MessageCompleted;
|
|
33
|
+
export declare function replyDelivery(observed?: ObservedMessages, scope?: () => string, interim?: InterimMessages): MessageCompleted;
|
|
12
34
|
export declare const deliverUnlessSilent: MessageCompleted;
|
|
13
35
|
export {};
|
|
@@ -20,9 +20,31 @@ function completionKind(message, finishReason) {
|
|
|
20
20
|
if (message.trim() === "") return "whitespace";
|
|
21
21
|
return isSilentReply(message) ? "silent_marker" : "reply";
|
|
22
22
|
}
|
|
23
|
-
|
|
23
|
+
const INTERIM_TURNS = 256;
|
|
24
|
+
function interimTexts(limit = 256) {
|
|
25
|
+
const turns = new Map();
|
|
26
|
+
return {
|
|
27
|
+
has: (turn, text) => turns.get(turn)?.has(text) ?? false,
|
|
28
|
+
add(turn, text) {
|
|
29
|
+
const texts = turns.get(turn) ?? new Set();
|
|
30
|
+
turns.delete(turn);
|
|
31
|
+
turns.set(turn, texts.add(text));
|
|
32
|
+
for (const oldest of turns.keys()) {
|
|
33
|
+
if (turns.size <= limit) break;
|
|
34
|
+
turns.delete(oldest);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function replyDelivery(observed, scope = () => "telegram", interim) {
|
|
24
40
|
return async (event, channel, context) => {
|
|
25
|
-
|
|
41
|
+
const kind = completionKind(event.message, event.finishReason);
|
|
42
|
+
const interimOn = interim?.enabled() === true;
|
|
43
|
+
const isInterim = kind === "tool_calls" && interimOn && completionKind(event.message) === "reply";
|
|
44
|
+
if (kind !== "reply" && !isInterim) return;
|
|
45
|
+
const turn = interimOn && typeof event.turnId === "string" && event.turnId !== "" ? JSON.stringify([context.session?.id ?? "", event.turnId]) : void 0;
|
|
46
|
+
const key = event.message.trim();
|
|
47
|
+
if (turn !== void 0 && interim.delivered.has(turn, key)) return;
|
|
26
48
|
const message = customerMessage(event.message, confirmationLocale(context.session?.auth.current ?? null, currentLocale()));
|
|
27
49
|
const principal = context.session?.auth.current;
|
|
28
50
|
const replyTo = principal?.attributes.replyToMessageId;
|
|
@@ -42,12 +64,16 @@ function replyDelivery(observed, scope = () => "telegram") {
|
|
|
42
64
|
messageIds.push(Number(result.id));
|
|
43
65
|
}
|
|
44
66
|
} catch (error) {
|
|
45
|
-
const reference = reportTelegramError("telegram_reply_delivery_failed", error, { messageIds });
|
|
67
|
+
const reference = reportTelegramError(isInterim ? "telegram_interim_delivery_failed" : "telegram_reply_delivery_failed", error, { messageIds });
|
|
46
68
|
throw new TelegramDeliveryError(reference, error instanceof TelegramDeliveryError ? error.delivery : "unknown", messageIds);
|
|
47
69
|
}
|
|
48
70
|
return messageIds;
|
|
49
71
|
});
|
|
50
72
|
const text = message.text;
|
|
73
|
+
if (isInterim) {
|
|
74
|
+
if (turn !== void 0) interim.delivered.add(turn, key);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
51
77
|
if (observed === void 0 || channel.telegram.chatType === "private") return;
|
|
52
78
|
const sender = principal?.authenticator === "segmentstream-telegram" ? principal.subject : void 0;
|
|
53
79
|
const username = principal?.attributes.username;
|
|
@@ -71,4 +97,4 @@ function replyDelivery(observed, scope = () => "telegram") {
|
|
|
71
97
|
};
|
|
72
98
|
}
|
|
73
99
|
const deliverUnlessSilent = replyDelivery();
|
|
74
|
-
export { completionKind, deliverUnlessSilent, isSilentReply, replyDelivery };
|
|
100
|
+
export { INTERIM_TURNS, completionKind, deliverUnlessSilent, interimTexts, isSilentReply, replyDelivery };
|
|
@@ -1,2 +1,8 @@
|
|
|
1
1
|
import type { TelegramChannelEvents } from "eve/channels/telegram";
|
|
2
|
-
|
|
2
|
+
import type { Conversations } from "../conversations.js";
|
|
3
|
+
import type { AgentIdentities } from "../identities.js";
|
|
4
|
+
/** Failure notices for members; an unrecoverable session failure is also recorded to the archive. */
|
|
5
|
+
export declare function telegramFailureEvents(input: {
|
|
6
|
+
readonly archive: Conversations;
|
|
7
|
+
readonly identities: () => AgentIdentities;
|
|
8
|
+
}): Pick<TelegramChannelEvents, "turn.failed" | "turn.cancelled" | "session.failed" | "authorization.completed" | "authorization.required">;
|