@segmentstream/agent 0.25.5 → 0.25.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -5
- package/dist/_chunks/{build-metadata-B_sSM5pA.mjs → build-metadata-Wt6SOKfE.mjs} +1 -1
- package/dist/extension/instructions.md +2 -1
- package/dist/extension/lib/build-metadata.mjs +1 -1
- package/dist/extension/lib/confirmations.d.ts +5 -0
- package/dist/extension/lib/confirmations.mjs +5 -1
- package/dist/extension/lib/ontology-file-call.mjs +10 -2
- package/dist/extension/lib/telegram/adapter.mjs +3 -3
- package/dist/extension/lib/telegram/durable-typing.d.ts +4 -0
- package/dist/extension/lib/telegram/durable-typing.mjs +6 -2
- package/dist/extension/lib/telegram/health.mjs +1 -1
- package/dist/extension/lib/telegram/lifecycle.mjs +1 -1
- package/dist/extension/lib/telegram/session-recovery.d.ts +5 -4
- package/dist/extension/lib/telegram/session-recovery.mjs +18 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -717,12 +717,15 @@ See [migration and verification](../../docs/agent-chat-delivery-migration.md).
|
|
|
717
717
|
|
|
718
718
|
If Eve's session lookup raises `RunKeyFetchError` with HTTP 404 and Vercel's
|
|
719
719
|
`not_found` / `Deployment not found.` response, the Telegram adapter records the
|
|
720
|
-
original diagnostic and
|
|
721
|
-
The smart gate still decides whether to reply; observing a message
|
|
722
|
-
and sends no failure notice.
|
|
723
|
-
|
|
724
|
-
|
|
720
|
+
original diagnostic and continues the address at a successor named by the address and
|
|
721
|
+
the unreachable run. The smart gate still decides whether to reply; observing a message
|
|
722
|
+
creates no session and sends no failure notice. Every deployment, independent request and
|
|
723
|
+
cold start selects the same successor, and Eve hands it over between deployments like any
|
|
724
|
+
healthy session. If the successor's own deployment is deleted later, the next successor
|
|
725
|
+
follows the same rule, up to four in a row. Archived conversation context remains
|
|
725
726
|
available, but the inaccessible session's model history and pending inputs cannot migrate.
|
|
727
|
+
`segmentstream history squash` deletes earlier deployments and so ends the sessions
|
|
728
|
+
they own; each affected address continues once at its successor.
|
|
726
729
|
|
|
727
730
|
Recovery applies only to session reads. Dispatch failures are never retried because
|
|
728
731
|
the inbox may already have accepted them. Other provider errors propagate to operator
|
|
@@ -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.25.
|
|
7
|
+
var version = "0.25.7";
|
|
8
8
|
function agentBuild(environment = process.env) {
|
|
9
9
|
return {
|
|
10
10
|
agent: version,
|
|
@@ -48,7 +48,8 @@ when it matters, tell the requester the action did not run and that they can ask
|
|
|
48
48
|
the tool boundary's workspace-authored confirmation buttons. Call the action to start that flow; never ask for
|
|
49
49
|
approval in prose or treat a text acknowledgement as approval. Wait for the button outcome.
|
|
50
50
|
When the tool schema offers `_confirmation`, supply {question, confirm, cancel, done,
|
|
51
|
-
cancelled, failed, expired?, edit?} in the chat's language
|
|
51
|
+
cancelled, failed, expired?, edit?} in the chat's language; a call needing a card without
|
|
52
|
+
valid copy is refused before any card. Describe the action and key
|
|
52
53
|
parameters in a plain single-line question (at most 300 characters); labels are at most
|
|
53
54
|
24 characters. No IDs, tool names, snake_case, URLs, credentials, or markup. The platform
|
|
54
55
|
adds trusted facts. Omit this argument on older tools without schema support.
|
|
@@ -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-Wt6SOKfE.mjs";
|
|
8
8
|
export { agentBuild };
|
|
@@ -39,6 +39,11 @@ export declare const confirmations: StateHandle<Record<string, Confirmation>>;
|
|
|
39
39
|
export declare function actionApproval(options: {
|
|
40
40
|
readonly labels: (actor: OntologyActor, action: string, input: Readonly<Record<string, unknown>>) => Promise<ActionAnnotations>;
|
|
41
41
|
readonly describe?: (actor: OntologyActor, action: string, input: Readonly<Record<string, unknown>>) => Promise<ConfirmationDescription>;
|
|
42
|
+
/**
|
|
43
|
+
* The call's `_confirmation` when the tool input does not carry it itself, such as a file-backed
|
|
44
|
+
* call whose arguments are snapshotted elsewhere. Defaults to the tool input's own field.
|
|
45
|
+
*/
|
|
46
|
+
readonly copy?: (actor: OntologyActor, input: Readonly<Record<string, unknown>>) => unknown;
|
|
42
47
|
readonly texts?: () => TelegramTexts;
|
|
43
48
|
readonly state?: StateHandle<Record<string, Confirmation>>;
|
|
44
49
|
readonly now?: () => number;
|
|
@@ -65,10 +65,13 @@ function actionApproval(options) {
|
|
|
65
65
|
console.warn("The ontology action permission check failed.", error);
|
|
66
66
|
return denied("The action is unavailable or the member no longer has permission.");
|
|
67
67
|
}
|
|
68
|
-
const
|
|
68
|
+
const offered = options.copy === void 0 ? ctx.toolInput?._confirmation : options.copy(member(memberId), ctx.toolInput ?? {});
|
|
69
|
+
const copy = labels.confirmation === false ? void 0 : confirmationCopy(offered);
|
|
69
70
|
const businessInput = Object.fromEntries(Object.entries(ctx.toolInput ?? {}).filter(([key]) => key !== "_confirmation"));
|
|
70
71
|
const posting = isChatPost(ctx.toolName);
|
|
71
72
|
let description;
|
|
73
|
+
const missingCopy = () => denied((offered === void 0 ? "This action needs a confirmation card and the call has no _confirmation. " : "The call's _confirmation is invalid. ") + "Make the call again with _confirmation (for a file call, in its arguments file) in the chat's language: a plain single-line question naming the action and its key records and amounts (at most 300 characters), and short confirm, cancel, done, cancelled and failed labels without emoji.");
|
|
74
|
+
if (posting && saved === void 0 && labels.confirmation === true && copy === void 0) return missingCopy();
|
|
72
75
|
if (posting) try {
|
|
73
76
|
description = await options.describe?.(member(memberId), ctx.toolName, businessInput);
|
|
74
77
|
const preview = description?.chatPost;
|
|
@@ -96,6 +99,7 @@ function actionApproval(options) {
|
|
|
96
99
|
}
|
|
97
100
|
if (saved !== void 0) return denied("The original call is waiting for its Confirm / Cancel buttons.");
|
|
98
101
|
if (!posting && !needsConfirmation(labels)) return "approved";
|
|
102
|
+
if (labels.confirmation === true && copy === void 0) return missingCopy();
|
|
99
103
|
try {
|
|
100
104
|
if (!posting) description = await options.describe?.(member(memberId), ctx.toolName, businessInput);
|
|
101
105
|
if (description === void 0) throw new Error("A trusted confirmation description is required.");
|
|
@@ -48,7 +48,15 @@ function ontologyFileCall(texts) {
|
|
|
48
48
|
},
|
|
49
49
|
describe: async (actor, _action, input) => {
|
|
50
50
|
const saved = bound(input, ownerOf(actor));
|
|
51
|
-
|
|
51
|
+
const business = Object.fromEntries(Object.entries(saved.arguments).filter(([key]) => key !== "_confirmation"));
|
|
52
|
+
return ontologyTools({ principal }).describe(actor, saved.name, business);
|
|
53
|
+
},
|
|
54
|
+
copy: (actor, input) => {
|
|
55
|
+
try {
|
|
56
|
+
return bound(input, ownerOf(actor)).arguments._confirmation;
|
|
57
|
+
} catch {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
52
60
|
},
|
|
53
61
|
texts
|
|
54
62
|
});
|
|
@@ -70,7 +78,7 @@ function ontologyFileCall(texts) {
|
|
|
70
78
|
}
|
|
71
79
|
return defineTool({
|
|
72
80
|
deferred: true,
|
|
73
|
-
description: "Call an ontology tool with the complete JSON object computed in a sandbox file. Use for script-generated arguments, or set save_result to save a permitted read's exact JSON result in the isolated sandbox for scripts, without copying rows through the model. Find the real tool and its schema through Eve search first; ordinary inline calls use Eve. Chat posting is excluded. Saving a result never permits a write tool or expands query limits.",
|
|
81
|
+
description: "Call an ontology tool with the complete JSON object computed in a sandbox file. Use for script-generated arguments, or set save_result to save a permitted read's exact JSON result in the isolated sandbox for scripts, without copying rows through the model. Find the real tool and its schema through Eve search first; ordinary inline calls use Eve. Chat posting is excluded. Saving a result never permits a write tool or expands query limits. When the tool's schema offers _confirmation and the call needs a card, put it in the file.",
|
|
74
82
|
inputSchema: {
|
|
75
83
|
type: "object",
|
|
76
84
|
properties: {
|
|
@@ -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-Wt6SOKfE.mjs";
|
|
28
28
|
import { attachmentLifecycle } from "./lifecycle.mjs";
|
|
29
29
|
import { gatewayImageDescriber } from "./images.mjs";
|
|
30
30
|
import { gatewayAttachmentDescriber } from "./attachment-description.mjs";
|
|
@@ -446,7 +446,7 @@ function segmentstreamTelegram(options) {
|
|
|
446
446
|
return {
|
|
447
447
|
...channel,
|
|
448
448
|
async receive(input, operations) {
|
|
449
|
-
operations = recoverableSessions(operations,
|
|
449
|
+
operations = recoverableSessions(operations, readHistory);
|
|
450
450
|
const target = input.target;
|
|
451
451
|
if (target.confirmationSessionId !== void 0) {
|
|
452
452
|
const address = target.confirmationAddress;
|
|
@@ -516,7 +516,7 @@ function gatedRoute(input) {
|
|
|
516
516
|
if (options.settings() === null) return new Response("not found", { status: 404 });
|
|
517
517
|
args = {
|
|
518
518
|
...args,
|
|
519
|
-
...recoverableSessions(args,
|
|
519
|
+
...recoverableSessions(args, input.readHistory)
|
|
520
520
|
};
|
|
521
521
|
let description;
|
|
522
522
|
const describe = () => description ??= args.describe();
|
|
@@ -11,6 +11,8 @@ export interface TelegramTypingSource {
|
|
|
11
11
|
export interface TypingCursor {
|
|
12
12
|
readonly index: number;
|
|
13
13
|
readonly turnId: string | null;
|
|
14
|
+
/** When the monitor first ran, in epoch milliseconds; continuations carry it forward. */
|
|
15
|
+
readonly startedAt?: number;
|
|
14
16
|
}
|
|
15
17
|
interface TypingEvent {
|
|
16
18
|
readonly type: string;
|
|
@@ -40,6 +42,8 @@ export interface TypingBlockResult {
|
|
|
40
42
|
readonly reason: string;
|
|
41
43
|
readonly sent: number;
|
|
42
44
|
}
|
|
45
|
+
/** The longest a turn shows typing; a longer turn keeps working without the indicator. */
|
|
46
|
+
export declare const MAX_TYPING_MS: number;
|
|
43
47
|
/** One bounded activity. Its durable parent continues independently of the webhook host. */
|
|
44
48
|
export declare function runTypingBlock(source: TelegramTypingSource, cursor: TypingCursor, dependencies: TypingBlockDependencies): Promise<TypingBlockResult>;
|
|
45
49
|
export declare function telegramTypingBlock(source: TelegramTypingSource, cursor: TypingCursor, sessionStatus: string): Promise<TypingBlockResult>;
|
|
@@ -12,18 +12,22 @@ import { p as reportTelegramError } from "../../../_chunks/inbound-request-xmrm-
|
|
|
12
12
|
import { connectionProxy } from "../connection-proxy.mjs";
|
|
13
13
|
import { sessions } from "eve/server";
|
|
14
14
|
const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
15
|
+
const MAX_TYPING_MS = 18e5;
|
|
15
16
|
async function runTypingBlock(source, cursor, dependencies) {
|
|
16
17
|
const deadline = dependencies.now() + 3e4;
|
|
18
|
+
const startedAt = cursor.startedAt ?? dependencies.now();
|
|
17
19
|
let index = cursor.index, turnId = cursor.turnId ?? source.turnId ?? null, sent = 0;
|
|
18
20
|
const result = (done, reason) => ({
|
|
19
21
|
cursor: {
|
|
20
22
|
index,
|
|
21
|
-
turnId
|
|
23
|
+
turnId,
|
|
24
|
+
startedAt
|
|
22
25
|
},
|
|
23
26
|
done,
|
|
24
27
|
reason,
|
|
25
28
|
sent
|
|
26
29
|
});
|
|
30
|
+
if (dependencies.now() - startedAt >= 18e5) return result(true, "typing_expired");
|
|
27
31
|
if ([
|
|
28
32
|
"completed",
|
|
29
33
|
"failed",
|
|
@@ -175,4 +179,4 @@ async function recordTelegramTypingStart(source, workflowRunId) {
|
|
|
175
179
|
});
|
|
176
180
|
}
|
|
177
181
|
}
|
|
178
|
-
export { recordTelegramTypingStart, runTypingBlock, telegramTypingBlock };
|
|
182
|
+
export { MAX_TYPING_MS, recordTelegramTypingStart, runTypingBlock, telegramTypingBlock };
|
|
@@ -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-Wt6SOKfE.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-Wt6SOKfE.mjs";
|
|
14
14
|
var AttachmentLifecycleNotPermitted = class extends Error {
|
|
15
15
|
code = "attachment_lifecycle_not_permitted";
|
|
16
16
|
};
|
|
@@ -2,9 +2,10 @@ import type { ChannelReceiveContext } from "eve/channels";
|
|
|
2
2
|
import type { HistoryReader } from "./sessions.js";
|
|
3
3
|
/**
|
|
4
4
|
* A deleted deployment can make the Workflow metadata key unreadable before Eve can hand off a session.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Eve cannot reset that owner, so the address continues at a successor named by the address and the
|
|
6
|
+
* unreachable run. Every deployment derives the same successor, and Eve hands it over between
|
|
7
|
+
* deployments like any healthy session; if its own deployment is later deleted, the next successor
|
|
8
|
+
* follows the same rule. Recover on a read, before dispatch. Healthy addresses keep their owner.
|
|
8
9
|
* No send is retried: a dispatch error can mean the durable inbox already accepted the message.
|
|
9
10
|
*/
|
|
10
|
-
export declare function recoverableSessions<State>(operations: ChannelReceiveContext<State>,
|
|
11
|
+
export declare function recoverableSessions<State>(operations: ChannelReceiveContext<State>, readHistory?: HistoryReader): ChannelReceiveContext<State>;
|
|
@@ -6,17 +6,18 @@ __eveDirname(__filename);
|
|
|
6
6
|
__eveCreateRequire(import.meta.url);
|
|
7
7
|
import { p as reportTelegramError } from "../../../_chunks/inbound-request-xmrm-49O.mjs";
|
|
8
8
|
import { createHash } from "node:crypto";
|
|
9
|
-
function
|
|
10
|
-
if (!(error instanceof Error) || error.name !== "RunKeyFetchError" || !("status" in error) || error.status !== 404) return
|
|
11
|
-
const
|
|
9
|
+
function strandedRun(error) {
|
|
10
|
+
if (!(error instanceof Error) || error.name !== "RunKeyFetchError" || !("status" in error) || error.status !== 404) return;
|
|
11
|
+
const match = /^Failed to fetch run key for (wrun_[\w-]+) \(deployment dpl_[\w-]+\): HTTP 404 [^\n]+? — (.+)$/u.exec(error.message);
|
|
12
12
|
try {
|
|
13
|
-
const response = JSON.parse(
|
|
14
|
-
return response?.error?.code === "not_found" && response.error.message === "Deployment not found.";
|
|
13
|
+
const response = JSON.parse(match?.[2] ?? "");
|
|
14
|
+
return response?.error?.code === "not_found" && response.error.message === "Deployment not found." ? match?.[1] : void 0;
|
|
15
15
|
} catch {
|
|
16
|
-
return
|
|
16
|
+
return;
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
-
|
|
19
|
+
const MAX_SUCCESSORS = 4;
|
|
20
|
+
function recoverableSessions(operations, readHistory) {
|
|
20
21
|
const replacements = new Map();
|
|
21
22
|
const readable = new Map();
|
|
22
23
|
const target = (address) => replacements.get(address) ?? address;
|
|
@@ -33,18 +34,21 @@ function recoverableSessions(operations, deploymentId, readHistory) {
|
|
|
33
34
|
return session;
|
|
34
35
|
}
|
|
35
36
|
async function resolveSession(address) {
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
let current = target(address);
|
|
38
|
+
for (let successors = 0;; successors += 1) try {
|
|
39
|
+
return await lookup(current);
|
|
38
40
|
} catch (error) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
const run = successors < MAX_SUCCESSORS ? strandedRun(error) : void 0;
|
|
42
|
+
if (run === void 0) throw error;
|
|
43
|
+
const digest = createHash("sha256").update(JSON.stringify([current, run])).digest("hex");
|
|
44
|
+
const replacement = `${current}:recovery:${digest}`;
|
|
41
45
|
reportTelegramError("session_resume_recovered", error, {
|
|
42
|
-
address,
|
|
46
|
+
address: current,
|
|
43
47
|
replacement,
|
|
44
|
-
|
|
48
|
+
strandedRun: run
|
|
45
49
|
});
|
|
46
50
|
replacements.set(address, replacement);
|
|
47
|
-
|
|
51
|
+
current = replacement;
|
|
48
52
|
}
|
|
49
53
|
}
|
|
50
54
|
async function source(address) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@segmentstream/agent",
|
|
3
|
-
"version": "0.25.
|
|
3
|
+
"version": "0.25.7",
|
|
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",
|