@segmentstream/agent 0.24.0 → 0.24.2
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 +3 -2
- package/dist/_chunks/{build-metadata-DDV7Rc51.mjs → build-metadata-BvsLNBnp.mjs} +1 -1
- package/dist/extension/lib/build-metadata.mjs +1 -1
- package/dist/extension/lib/confirmation-description.d.ts +5 -0
- package/dist/extension/lib/confirmation-description.mjs +11 -3
- package/dist/extension/lib/confirmations.d.ts +1 -1
- package/dist/extension/lib/confirmations.mjs +2 -0
- package/dist/extension/lib/conversation-runtime.mjs +14 -1
- package/dist/extension/lib/stored-files.d.ts +23 -8
- package/dist/extension/lib/stored-files.mjs +53 -42
- package/dist/extension/lib/telegram/adapter.mjs +1 -1
- package/dist/extension/lib/telegram/health.mjs +1 -1
- package/dist/extension/lib/telegram/lifecycle.mjs +1 -1
- package/dist/extension/tools/attachments_stage.d.ts +2 -1
- package/dist/extension/tools/attachments_stage.mjs +53 -20
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -267,8 +267,9 @@ promotes it like any attachment. The ontology accepts that source from Ontology
|
|
|
267
267
|
`segmentstream__attachments_stage` is the way in: given a chat attachment's `id`, or a record's
|
|
268
268
|
`entity`, `id` and optional `property`, the runtime reads the file as the member through the same
|
|
269
269
|
grants as `attachments_read`, checks its SHA-256 and writes it to
|
|
270
|
-
`/workspace/.segmentstream/files/<first 16 hex of the digest>/<safe name>`, answering the path.
|
|
271
|
-
|
|
270
|
+
`/workspace/.segmentstream/files/<first 16 hex of the digest>/<safe name>`, answering the path. A
|
|
271
|
+
stored file staged again is granted again but not transferred while that path holds the same
|
|
272
|
+
SHA-256. No URL, header or credential enters the sandbox. `segmentstream__read_file` keeps eve's model output,
|
|
272
273
|
so PNG, JPEG, GIF and WebP images up to 3 MiB and PDFs up to 20 MiB reach the model as files.
|
|
273
274
|
|
|
274
275
|
For a script-generated JSON payload, use `segmentstream__ontology_call_file` with the bare tool
|
|
@@ -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
|
-
var version = "0.24.
|
|
7
|
+
var version = "0.24.2";
|
|
8
8
|
function agentBuild(environment = process.env) {
|
|
9
9
|
return {
|
|
10
10
|
agent: version,
|
|
@@ -4,5 +4,5 @@ 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-BvsLNBnp.mjs";
|
|
8
8
|
export { agentBuild };
|
|
@@ -16,6 +16,11 @@ export interface ConfirmationDescription {
|
|
|
16
16
|
readonly chatPost?: ChatPostPreview;
|
|
17
17
|
readonly warning?: boolean;
|
|
18
18
|
}
|
|
19
|
+
/** A target the card cannot name, by its field's safe label, so the model learns which one and why. */
|
|
20
|
+
export declare class UnsafeConfirmationTarget extends Error {
|
|
21
|
+
readonly label: string;
|
|
22
|
+
constructor(label: string);
|
|
23
|
+
}
|
|
19
24
|
export declare function object(value: unknown): Record<string, unknown>;
|
|
20
25
|
export declare function humanize(value: string): string;
|
|
21
26
|
/** Reject machine identifiers and structured data even in authored labels or resolved titles. */
|
|
@@ -5,6 +5,14 @@ const __filename = __eveFileURLToPath(import.meta.url);
|
|
|
5
5
|
__eveDirname(__filename);
|
|
6
6
|
__eveCreateRequire(import.meta.url);
|
|
7
7
|
import { isTechnicalText } from "./customer-text-safety.mjs";
|
|
8
|
+
var UnsafeConfirmationTarget = class extends Error {
|
|
9
|
+
label;
|
|
10
|
+
constructor(label) {
|
|
11
|
+
super(`Every confirmation target requires an authorized, safe record title: ${label}.`);
|
|
12
|
+
this.label = label;
|
|
13
|
+
this.name = "UnsafeConfirmationTarget";
|
|
14
|
+
}
|
|
15
|
+
};
|
|
8
16
|
function object(value) {
|
|
9
17
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
10
18
|
}
|
|
@@ -16,7 +24,7 @@ function humanText(value, maximum = 180) {
|
|
|
16
24
|
const text = value.trim();
|
|
17
25
|
if (text === "" || text.length > maximum || isTechnicalText(text) || [
|
|
18
26
|
/[\p{Cc}\p{Cf}\p{Zl}\p{Zp}<>`*_~|\\\[\]{}]|^(?:#{1,6}|[-+])\s/u,
|
|
19
|
-
/[a-z][a-z0-9+.-]*:\/\/|www\.|\b[a-z][a-z0-9]*\.[a-z][a-z_0-9]
|
|
27
|
+
/[a-z][a-z0-9+.-]*:\/\/|www\.|\b[a-z][a-z0-9]*\.[a-z][a-z_0-9]+\b/iu,
|
|
20
28
|
/&(?:#\d+|#x[0-9a-f]+|[a-z]+);/iu,
|
|
21
29
|
/\bBearer\s+|\b(?:password|secret|token|credential|api[ _-]?key)\s*[:=]/iu,
|
|
22
30
|
/\b(?:sk|ssw|ghp)[_-][a-z0-9]+/iu,
|
|
@@ -70,7 +78,7 @@ async function describeConfirmation(input) {
|
|
|
70
78
|
const value = (await read.get(cacheKey))?.[target.title];
|
|
71
79
|
title = value === item ? void 0 : factText(value, 3584);
|
|
72
80
|
}
|
|
73
|
-
if (title === void 0) throw new
|
|
81
|
+
if (title === void 0) throw new UnsafeConfirmationTarget(label);
|
|
74
82
|
values.push(title);
|
|
75
83
|
targets.push({
|
|
76
84
|
label,
|
|
@@ -100,4 +108,4 @@ async function describeConfirmation(input) {
|
|
|
100
108
|
...targets.length === 0 ? {} : { targets }
|
|
101
109
|
};
|
|
102
110
|
}
|
|
103
|
-
export { describeConfirmation, fieldSchema, humanText, humanize, object };
|
|
111
|
+
export { UnsafeConfirmationTarget, describeConfirmation, fieldSchema, humanText, humanize, object };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type StateHandle } from "eve/context";
|
|
2
2
|
import type { ApprovalConfiguration } from "eve/tools/approval";
|
|
3
3
|
import { type OntologyActor } from "./actor.js";
|
|
4
|
-
import type
|
|
4
|
+
import { type ConfirmationDescription } from "./confirmation-description.js";
|
|
5
5
|
import { type ConfirmationCopy } from "./telegram/confirmation-text.js";
|
|
6
6
|
import { type TelegramTexts } from "./telegram/texts.js";
|
|
7
7
|
export declare const CONFIRMATION_TTL_MS: number;
|
|
@@ -6,6 +6,7 @@ __eveDirname(__filename);
|
|
|
6
6
|
__eveCreateRequire(import.meta.url);
|
|
7
7
|
import { withTelegramTexts } from "./telegram/texts.mjs";
|
|
8
8
|
import { i as memberIdOf } from "../../_chunks/principal-DMNXj4PX.mjs";
|
|
9
|
+
import { UnsafeConfirmationTarget } from "./confirmation-description.mjs";
|
|
9
10
|
import { confirmationCopy, confirmationLocale, renderConfirmation } from "./telegram/confirmation-text.mjs";
|
|
10
11
|
import { chatPostCard } from "./telegram/chat-post-card.mjs";
|
|
11
12
|
import { actorOf, member } from "./actor.mjs";
|
|
@@ -100,6 +101,7 @@ function actionApproval(options) {
|
|
|
100
101
|
if (description === void 0) throw new Error("A trusted confirmation description is required.");
|
|
101
102
|
} catch (error) {
|
|
102
103
|
console.warn("The confirmation description could not be read.", error);
|
|
104
|
+
if (error instanceof UnsafeConfirmationTarget) return denied(`No confirmation card can name the ${error.label}: its title is missing, unreadable, or looks like a file name, link or identifier. Another call will be refused the same way; tell the member, who can rename that record or make the change in the app.`);
|
|
103
105
|
return denied("Trusted confirmation facts are incomplete, unsafe or too long. Resolve every target title and narrow the input before making a new call.");
|
|
104
106
|
}
|
|
105
107
|
description = description === void 0 ? void 0 : {
|
|
@@ -127,7 +127,20 @@ function conversationRecorder(archive = conversations(), closing = async (messag
|
|
|
127
127
|
errorText: contentText(event.data.error?.message, CONTENT_CAPS.error)
|
|
128
128
|
}));
|
|
129
129
|
if (event.type === "turn.settled" || event.type === "session.ended") await recordModelServiceAlert(archive, base, event.data.error);
|
|
130
|
-
} else if (event.type === "
|
|
130
|
+
} else if (event.type === "model.retried") await archive.append(conversationEvent({
|
|
131
|
+
...base,
|
|
132
|
+
eventId,
|
|
133
|
+
kind: "decision",
|
|
134
|
+
decision: "silent",
|
|
135
|
+
reason: event.type,
|
|
136
|
+
status: "failed",
|
|
137
|
+
argsSummary: JSON.stringify({
|
|
138
|
+
attempt: event.data.attempt,
|
|
139
|
+
...typeof event.data.error?.code === "string" ? { code: event.data.error.code } : {}
|
|
140
|
+
}),
|
|
141
|
+
errorText: contentText(event.data.error?.message, CONTENT_CAPS.error)
|
|
142
|
+
}));
|
|
143
|
+
else if (event.type === "content.completed" && event.data.kind === "text" && event.data.phase === "reply" && !event.data.interrupted) {
|
|
131
144
|
const message = typeof event.data.value === "string" ? event.data.value : "";
|
|
132
145
|
const kind = completionKind(message);
|
|
133
146
|
const silent = kind !== "reply";
|
|
@@ -1,17 +1,32 @@
|
|
|
1
1
|
import type { SessionAuthContext } from "eve/context";
|
|
2
2
|
import { ontologyTools } from "./ontology-tools.js";
|
|
3
|
+
/** A stored file's immutable revision metadata, as its download grant states it. */
|
|
4
|
+
export interface StoredFile {
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly filename: string;
|
|
7
|
+
readonly contentType: string;
|
|
8
|
+
readonly size: number;
|
|
9
|
+
readonly sha256: string;
|
|
10
|
+
}
|
|
3
11
|
/** A permanent blob is read through the same current-member grant used by the ontology UI. */
|
|
4
12
|
export declare function readStoredFile(input: {
|
|
5
13
|
readonly entity: string;
|
|
6
14
|
readonly id: string;
|
|
7
15
|
readonly property?: string;
|
|
8
16
|
}, principal: SessionAuthContext | null, options?: Parameters<typeof ontologyTools>[0]): Promise<{
|
|
9
|
-
attachment:
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
attachment: StoredFile;
|
|
18
|
+
bytes: Buffer<ArrayBufferLike>;
|
|
19
|
+
}>;
|
|
20
|
+
/**
|
|
21
|
+
* The member's current grant for a stored file: its revision metadata at once, and its bytes only
|
|
22
|
+
* when read. Every call checks the member's access; a caller that already holds bytes with the
|
|
23
|
+
* same digest need not transfer them again.
|
|
24
|
+
*/
|
|
25
|
+
export declare function storedFileGrant(input: {
|
|
26
|
+
readonly entity: string;
|
|
27
|
+
readonly id: string;
|
|
28
|
+
readonly property?: string;
|
|
29
|
+
}, principal: SessionAuthContext | null, options?: Parameters<typeof ontologyTools>[0]): Promise<{
|
|
30
|
+
readonly file: StoredFile;
|
|
31
|
+
read(): Promise<Buffer>;
|
|
17
32
|
}>;
|
|
@@ -11,6 +11,13 @@ import { member } from "./actor.mjs";
|
|
|
11
11
|
import { OntologyFileUnavailable, ontologyTools } from "./ontology-tools.mjs";
|
|
12
12
|
import { createHash } from "node:crypto";
|
|
13
13
|
async function readStoredFile(input, principal, options = {}) {
|
|
14
|
+
const grant = await storedFileGrant(input, principal, options);
|
|
15
|
+
return {
|
|
16
|
+
attachment: grant.file,
|
|
17
|
+
bytes: await grant.read()
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
async function storedFileGrant(input, principal, options = {}) {
|
|
14
21
|
const memberId = memberIdOf(principal);
|
|
15
22
|
if (memberId === void 0) throw new Error("A current member is required to read a stored file.");
|
|
16
23
|
const tools = ontologyTools({
|
|
@@ -33,48 +40,52 @@ async function readStoredFile(input, principal, options = {}) {
|
|
|
33
40
|
const headers = object(grant.headers);
|
|
34
41
|
const size = file.size;
|
|
35
42
|
const secrets = new Set([...typeof grant.url === "string" ? [grant.url] : [], ...Object.values(headers).filter((value) => typeof value === "string")]);
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
43
|
+
const unreadable = (error) => new Error("The permanent file could not be read.", { cause: redactDiagnostic(error, secrets) });
|
|
44
|
+
if (file.id !== input.id || file.entity !== input.entity || file.property !== (input.property ?? "content") || grant.method !== "GET" || typeof file.filename !== "string" || typeof file.content_type !== "string" || typeof file.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(file.sha256) || typeof size !== "number" || !Number.isSafeInteger(size) || size < 0 || size > 26214400 || typeof grant.url !== "string" || !URL.canParse(grant.url) || new URL(grant.url).protocol !== "https:" || Object.values(headers).some((value) => typeof value !== "string")) throw unreadable(new Error("The stored file grant or bounded metadata is invalid.", { cause: {
|
|
45
|
+
file,
|
|
46
|
+
download: grant
|
|
47
|
+
} }));
|
|
48
|
+
const url = grant.url, sha256 = file.sha256;
|
|
49
|
+
return {
|
|
50
|
+
file: {
|
|
51
|
+
id: input.id,
|
|
52
|
+
filename: file.filename,
|
|
53
|
+
contentType: file.content_type,
|
|
54
|
+
size,
|
|
55
|
+
sha256
|
|
56
|
+
},
|
|
57
|
+
async read() {
|
|
58
|
+
try {
|
|
59
|
+
const response = await (options.fetch ?? fetch)(url, {
|
|
60
|
+
method: "GET",
|
|
61
|
+
headers: Object.fromEntries(Object.entries(headers).map(([name, value]) => [name, String(value)])),
|
|
62
|
+
redirect: "error",
|
|
63
|
+
signal: AbortSignal.timeout(12e4)
|
|
64
|
+
});
|
|
65
|
+
if (!response.ok || response.body === null) throw new Error(`Stored file transfer refused (HTTP ${response.status}).`);
|
|
66
|
+
const reader = response.body.getReader(), chunks = [];
|
|
67
|
+
let received = 0;
|
|
68
|
+
try {
|
|
69
|
+
for (;;) {
|
|
70
|
+
const chunk = await reader.read();
|
|
71
|
+
if (chunk.done) break;
|
|
72
|
+
received += chunk.value.byteLength;
|
|
73
|
+
if (received > size) throw new Error("Stored file transfer exceeded its declared size.");
|
|
74
|
+
chunks.push(chunk.value);
|
|
75
|
+
}
|
|
76
|
+
} finally {
|
|
77
|
+
await reader.cancel().catch((error) => {
|
|
78
|
+
console.warn("Stored file reader cleanup failed.", redactDiagnostic(error, secrets));
|
|
79
|
+
});
|
|
80
|
+
reader.releaseLock();
|
|
81
|
+
}
|
|
82
|
+
const bytes = Buffer.concat(chunks);
|
|
83
|
+
if (bytes.byteLength !== size || createHash("sha256").update(bytes).digest("hex") !== sha256) throw new Error("Stored file bytes differ from their immutable revision metadata.");
|
|
84
|
+
return bytes;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
throw unreadable(error);
|
|
57
87
|
}
|
|
58
|
-
} finally {
|
|
59
|
-
await reader.cancel().catch((error) => {
|
|
60
|
-
console.warn("Stored file reader cleanup failed.", redactDiagnostic(error, secrets));
|
|
61
|
-
});
|
|
62
|
-
reader.releaseLock();
|
|
63
88
|
}
|
|
64
|
-
|
|
65
|
-
if (bytes.byteLength !== size || createHash("sha256").update(bytes).digest("hex") !== file.sha256) throw new Error("Stored file bytes differ from their immutable revision metadata.");
|
|
66
|
-
return {
|
|
67
|
-
attachment: {
|
|
68
|
-
id: input.id,
|
|
69
|
-
filename: file.filename,
|
|
70
|
-
contentType: file.content_type,
|
|
71
|
-
size,
|
|
72
|
-
sha256: file.sha256
|
|
73
|
-
},
|
|
74
|
-
bytes
|
|
75
|
-
};
|
|
76
|
-
} catch (error) {
|
|
77
|
-
throw new Error("The permanent file could not be read.", { cause: redactDiagnostic(error, secrets) });
|
|
78
|
-
}
|
|
89
|
+
};
|
|
79
90
|
}
|
|
80
|
-
export { readStoredFile };
|
|
91
|
+
export { readStoredFile, storedFileGrant };
|
|
@@ -24,7 +24,7 @@ import { MAXIMUM_DOWNLOAD_BYTES, connectionProxy } from "../connection-proxy.mjs
|
|
|
24
24
|
import { dispatchStructuredMessage, structuredMessage } from "./structured.mjs";
|
|
25
25
|
import { scopedAddress, sessionPolicy } from "./sessions.mjs";
|
|
26
26
|
import { handleWorkspaceCallback, isWorkspaceCallback } from "./callbacks.mjs";
|
|
27
|
-
import { t as agentBuild } from "../../../_chunks/build-metadata-
|
|
27
|
+
import { t as agentBuild } from "../../../_chunks/build-metadata-BvsLNBnp.mjs";
|
|
28
28
|
import { attachmentLifecycle } from "./lifecycle.mjs";
|
|
29
29
|
import { gatewayImageDescriber } from "./images.mjs";
|
|
30
30
|
import { gatewayAttachmentDescriber } from "./attachment-description.mjs";
|
|
@@ -5,7 +5,7 @@ const __filename = __eveFileURLToPath(import.meta.url);
|
|
|
5
5
|
__eveDirname(__filename);
|
|
6
6
|
__eveCreateRequire(import.meta.url);
|
|
7
7
|
import { runtimeRequestFailure } from "./runtime-request.mjs";
|
|
8
|
-
import { t as agentBuild } from "../../../_chunks/build-metadata-
|
|
8
|
+
import { t as agentBuild } from "../../../_chunks/build-metadata-BvsLNBnp.mjs";
|
|
9
9
|
import { GET } from "eve/channels";
|
|
10
10
|
const HEALTH_ROUTE = "/eve/v1/telegram/health";
|
|
11
11
|
function healthRoute(input) {
|
|
@@ -10,7 +10,7 @@ import { n as currentInboundRequest, p as reportTelegramError } from "../../../_
|
|
|
10
10
|
import { member } from "../actor.mjs";
|
|
11
11
|
import { needsConfirmation } from "../confirmations.mjs";
|
|
12
12
|
import { OntologyActionNotPermitted, actionToolName, ontologyTools } from "../ontology-tools.mjs";
|
|
13
|
-
import { t as agentBuild } from "../../../_chunks/build-metadata-
|
|
13
|
+
import { t as agentBuild } from "../../../_chunks/build-metadata-BvsLNBnp.mjs";
|
|
14
14
|
var AttachmentLifecycleNotPermitted = class extends Error {
|
|
15
15
|
code = "attachment_lifecycle_not_permitted";
|
|
16
16
|
};
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* so scripts can work on its bytes: a chat attachment by id, or a permanent record's file by entity
|
|
4
4
|
* and record id. The runtime reads it as the member, through the same grants as
|
|
5
5
|
* `attachments_read`, checks its SHA-256 and writes it under `/workspace/.segmentstream/files/`.
|
|
6
|
-
*
|
|
6
|
+
* A stored file is granted on every call, but its bytes are not transferred again while its digest
|
|
7
|
+
* path in the sandbox holds the same SHA-256. No URL, header or credential enters the sandbox. The way back is `attachments_promote` with a
|
|
7
8
|
* path. Offered only when the sandbox is on, and never to a task session, which has no member
|
|
8
9
|
* files.
|
|
9
10
|
*/
|
|
@@ -9,7 +9,7 @@ import segmentstream from "../extension.mjs";
|
|
|
9
9
|
import { actorOf } from "../lib/actor.mjs";
|
|
10
10
|
import { sessionSandbox } from "../lib/sandbox-session.mjs";
|
|
11
11
|
import { attachmentOwner, optionalString, stringArgument, toolAttachments } from "../lib/attachment-tools.mjs";
|
|
12
|
-
import {
|
|
12
|
+
import { storedFileGrant } from "../lib/stored-files.mjs";
|
|
13
13
|
import { createHash } from "node:crypto";
|
|
14
14
|
import { posix } from "node:path";
|
|
15
15
|
import { defineDynamic, defineTool } from "eve/tools";
|
|
@@ -21,6 +21,15 @@ function stagedFileName(filename) {
|
|
|
21
21
|
const stem = (dot > 0 ? base.slice(0, dot) : base).normalize("NFKD").replace(/[^\w.-]+/g, "_").replace(/^[._-]+|[._-]+$/g, "").slice(0, 120);
|
|
22
22
|
return `${stem === "" ? "file" : stem}${extension}`;
|
|
23
23
|
}
|
|
24
|
+
async function holds(sandbox, path, sha256) {
|
|
25
|
+
try {
|
|
26
|
+
const hashed = await sandbox.run({ command: `sha256sum -- '${path.replaceAll("'", `'\\''`)}' 2>/dev/null` });
|
|
27
|
+
return hashed.exitCode === 0 && hashed.stdout.slice(0, 64) === sha256;
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.warn("A staged file could not be checked; staging it again.", error);
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
24
33
|
function stagedFilePath(sha256, filename) {
|
|
25
34
|
return `${STAGED_FILES_DIRECTORY}/${sha256.slice(0, 16)}/${stagedFileName(filename)}`;
|
|
26
35
|
}
|
|
@@ -51,27 +60,51 @@ const tool = defineTool({
|
|
|
51
60
|
const entity = optionalString(input, "entity");
|
|
52
61
|
const property = optionalString(input, "property");
|
|
53
62
|
if (entity === void 0 && property !== void 0) throw new TypeError("property requires entity.");
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
const sandboxOf = () => sessionSandbox(ctx);
|
|
64
|
+
let staged;
|
|
65
|
+
if (entity === void 0) {
|
|
66
|
+
const { attachment, bytes } = await toolAttachments().read(attachmentOwner({
|
|
67
|
+
principal: ctx.session.auth.current,
|
|
68
|
+
sessionId: ctx.session.id
|
|
69
|
+
}), id);
|
|
70
|
+
const sha256 = createHash("sha256").update(bytes).digest("hex");
|
|
71
|
+
if (sha256 !== attachment.sha256) throw new Error("The file's bytes differ from its recorded SHA-256; nothing was staged.");
|
|
72
|
+
const path = stagedFilePath(sha256, attachment.filename);
|
|
73
|
+
await (await sandboxOf()).writeBinaryFile({
|
|
74
|
+
path,
|
|
75
|
+
content: bytes
|
|
76
|
+
});
|
|
77
|
+
staged = {
|
|
78
|
+
path,
|
|
79
|
+
file: {
|
|
80
|
+
...attachment,
|
|
81
|
+
size: bytes.byteLength
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
} else {
|
|
85
|
+
const grant = await storedFileGrant({
|
|
86
|
+
entity,
|
|
87
|
+
id,
|
|
88
|
+
...property === void 0 ? {} : { property }
|
|
89
|
+
}, ctx.session.auth.current);
|
|
90
|
+
const path = stagedFilePath(grant.file.sha256, grant.file.filename);
|
|
91
|
+
const sandbox = await sandboxOf();
|
|
92
|
+
if (!await holds(sandbox, path, grant.file.sha256)) await sandbox.writeBinaryFile({
|
|
93
|
+
path,
|
|
94
|
+
content: await grant.read()
|
|
95
|
+
});
|
|
96
|
+
staged = {
|
|
97
|
+
path,
|
|
98
|
+
file: grant.file
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
const { path, file } = staged;
|
|
69
102
|
return workflowToolResult({
|
|
70
103
|
path,
|
|
71
|
-
filename:
|
|
72
|
-
content_type:
|
|
73
|
-
size:
|
|
74
|
-
sha256,
|
|
104
|
+
filename: file.filename,
|
|
105
|
+
content_type: file.contentType,
|
|
106
|
+
size: file.size,
|
|
107
|
+
sha256: file.sha256,
|
|
75
108
|
...entity === void 0 ? { attachment_id: id } : {
|
|
76
109
|
entity,
|
|
77
110
|
record_id: id,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@segmentstream/agent",
|
|
3
|
-
"version": "0.24.
|
|
3
|
+
"version": "0.24.2",
|
|
4
4
|
"description": "Mount a SegmentStream workspace's agent on eve: the ontology as tools, acting as the member who writes, and Telegram through the workspace's connection.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "SegmentStream",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
},
|
|
75
75
|
"dependencies": {
|
|
76
76
|
"@chat-adapter/telegram": "4.41.0",
|
|
77
|
-
"@segmentstream-cloud/ontology": "0.
|
|
77
|
+
"@segmentstream-cloud/ontology": "0.79.0",
|
|
78
78
|
"@vercel/oidc": "^3.8.4",
|
|
79
79
|
"ai": "^7.0.137",
|
|
80
80
|
"csv-parse": "^7.0.3",
|