@segmentstream/agent 0.5.0 → 0.5.1

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.
@@ -5,14 +5,15 @@ import { type Environment } from "./environment.js";
5
5
  import { type RuntimeToken } from "./runtime-token.js";
6
6
  /**
7
7
  * What the `segmentstream__attachments_*` tools share. The owner of every call comes from the turn:
8
- * the member and chat on the session's principal, which the channel set from the pairing, and the
9
- * session's own id. A tool's arguments name only the attachment and what to do with it.
8
+ * the member and chat on the session's principal, which the channel set from the pairing or a
9
+ * verified member request, and the session's own id. A tool's arguments name only the attachment
10
+ * and what to do with it.
10
11
  */
11
12
  /** Maximum characters in a read/query response; remaining data is available by continuation. */
12
13
  export declare const MAXIMUM_TEXT_CHARACTERS = 50000;
13
14
  /** The largest file `attachments_read` hands the model inline. */
14
15
  export declare const MAXIMUM_INLINE_BYTES: number;
15
- /** The turn has no member and chat to own attachments, such as a caller who never paired. */
16
+ /** The turn has no member and chat to authorize attachments. */
16
17
  export declare class AttachmentOwnerError extends Error {
17
18
  readonly name = "AttachmentOwnerError";
18
19
  }
@@ -17,9 +17,11 @@ var AttachmentOwnerError = class extends Error {
17
17
  name = "AttachmentOwnerError";
18
18
  };
19
19
  function attachmentOwner(input) {
20
- const memberId = memberIdOf(input.principal);
20
+ const attributes = input.principal?.attributes;
21
+ const requesterId = attributes?.requesterKind === "member" && attributes.chatType === "group" ? attributes.requesterId : void 0;
22
+ const memberId = memberIdOf(input.principal) ?? (typeof requesterId === "string" ? requesterId : void 0);
21
23
  const chatId = input.principal?.attributes.chatId;
22
- if (memberId === void 0 || typeof chatId !== "string" || chatId === "") throw new AttachmentOwnerError("Attachments are available only to a paired member in a chat with the agent.");
24
+ if (memberId === void 0 || memberId === "" || typeof chatId !== "string" || chatId === "") throw new AttachmentOwnerError("Attachments require a workspace member in a chat with the agent.");
23
25
  return {
24
26
  memberId,
25
27
  conversation: telegramConversation(chatId),
@@ -2,7 +2,7 @@ import type { RuntimeToken } from "./runtime-token.js";
2
2
  /**
3
3
  * The ontology service's attachment routes (`docs/agent.md`, "Attachments"). A file a member sends
4
4
  * the agent is stored once in the workspace's private file storage, for 24 hours, and belongs to
5
- * that member, the chat it arrived in and the agent session that first reads it. The agent calls
5
+ * the chat's authorized members in groups, or its sender and session in private chats. The agent calls
6
6
  * these routes as the deployment, with its OIDC token, naming the member, the chat and the session
7
7
  * from the turn's own context; nothing a model writes reaches these headers.
8
8
  */
@@ -14,7 +14,7 @@ export declare const CONVERSATION_HEADER = "X-SegmentStream-Agent-Conversation";
14
14
  export declare const SESSION_HEADER = "X-SegmentStream-Agent-Session";
15
15
  /** The URL protocol of a file part that names a stored attachment instead of a Telegram file. */
16
16
  export declare const ATTACHMENT_URL_PROTOCOL = "segmentstream-attachment:";
17
- /** Whose attachment it is, from the turn: the member, the chat and the agent session. */
17
+ /** Who asks for attachment access, from the turn: the member, chat and agent session. */
18
18
  export interface AttachmentOwner {
19
19
  readonly memberId: string;
20
20
  /** The chat, such as `telegram:12345`. */
@@ -68,7 +68,7 @@ async function stageFiles(input) {
68
68
  }
69
69
  function attachmentsContext(input) {
70
70
  if (input.files.size === 0) return [];
71
- const lines = ["<attachments>", `The member's files from this message, kept for 24 hours for this member in this chat only. Read, discard or promote them with the segmentstream__attachments_* tools and these ids; promote only what should be kept.`];
71
+ const lines = ["<attachments>", `Files from this message, kept for 24 hours ` + (input.group ? "for authorized members of this group only. " : "for this member in this private chat only. ") + "Read, discard or promote them with the segmentstream__attachments_* tools and these ids; promote only what should be kept."];
72
72
  for (const staged of input.files.values()) lines.push(staged.status === "stored" ? `- id: ${staged.attachment.id}, filename: ${JSON.stringify(staged.attachment.filename)}, type: ${staged.attachment.contentType}, size: ${staged.attachment.size} bytes, expires_at: ${staged.attachment.expiresAt}` : `- ${JSON.stringify(staged.filename)}: not received, ${staged.reason}`);
73
73
  if (input.group) lines.push("This is a group chat: everyone in it shares this conversation's history, so do not repeat a private file's contents here. Suggest sending private files in a direct message.");
74
74
  lines.push("</attachments>");
@@ -284,6 +284,7 @@ async function handleInboundMessage(input) {
284
284
  dependencies.sessionPolicy.delivered(address);
285
285
  }
286
286
  await context.telegram.startTyping();
287
+ await report;
287
288
  const files = sent.attachments.length === 0 || dependencies.attachments === void 0 || input.request === void 0 ? new Map() : await stageFiles({
288
289
  message: sent,
289
290
  memberId: lookup.member.id,
@@ -291,7 +292,6 @@ async function handleInboundMessage(input) {
291
292
  download: dependencies.download
292
293
  });
293
294
  if (input.request !== void 0 && files.size > 0) input.request.files = files;
294
- await report;
295
295
  dependencies.observed.remember(chat, observedMessage(sent, text, lookup.member));
296
296
  const principal = telegramPrincipal({
297
297
  message: sent,
@@ -76,6 +76,7 @@ function requestPrincipal(request) {
76
76
  subject: requester.id,
77
77
  attributes: {
78
78
  requesterId: requester.id,
79
+ requesterKind: requester.kind,
79
80
  requesterName: requester.name,
80
81
  roles: requester.roles,
81
82
  chatId: target.chatId,
@@ -94,7 +95,10 @@ function requestContext(request) {
94
95
  "If the addressed person has a linked Telegram username, mention them with @username; otherwise address them by name without a tag. Never invent a username."
95
96
  ];
96
97
  if (request.documentIds !== void 0 && request.documentIds.length > 0) lines.push(`documentIds: ${JSON.stringify(request.documentIds)}`, "Send these documents with the question using segmentstream__attachments_send in this chat. Use the question as the caption (at most 1024 characters), one question per message. In groups send documents only when the chat's purpose allows; never send restricted documents there.");
97
- if (requester.kind === "service" || !requester.actAs) lines.push(`${requester.kind === "service" ? "The requester is a service, not a member" : "The requester has not paired a Telegram account with the agent"}, so this turn runs without a member and the ontology is not available in it. Each member who replies acts with their own permissions.`);
98
+ if (requester.kind === "service" || !requester.actAs) {
99
+ lines.push(`${requester.kind === "service" ? "The requester is a service, not a member" : "The requester has not paired a Telegram account with the agent"}, so this turn runs without a member and the ontology is not available in it. Each member who replies acts with their own permissions.`);
100
+ if (requester.kind === "member" && request.target.kind === "group") lines.push("Temporary attachments in this group are available through segmentstream__attachments_* when the requester is a current chat participant or chat administrator. Promotion still requires the requester's permission for the target ontology action.");
101
+ }
98
102
  lines.push("</agent_request>");
99
103
  return lines.join("\n");
100
104
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@segmentstream/agent",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
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",