@vellumai/assistant 0.12.2-staging.7 → 0.12.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.
Files changed (120) hide show
  1. package/docs/architecture/memory.md +2 -11
  2. package/docs/desktop-browser-cli.md +2 -4
  3. package/node_modules/@vellumai/environments/src/shell.test.ts +0 -21
  4. package/node_modules/@vellumai/environments/src/shell.ts +0 -24
  5. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +2 -8
  6. package/openapi.yaml +2 -6
  7. package/package.json +1 -1
  8. package/scripts/smoke-desktop-browser-cli.ts +0 -1
  9. package/src/__tests__/agent-loop.test.ts +0 -124
  10. package/src/__tests__/approval-interception-trust-gates.test.ts +0 -40
  11. package/src/__tests__/channel-approval.test.ts +14 -9
  12. package/src/__tests__/conversation-agent-loop.test.ts +0 -25
  13. package/src/__tests__/plugin-import-boundary-guard.test.ts +1 -0
  14. package/src/__tests__/run-conversation-turn-persistence.test.ts +1 -138
  15. package/src/__tests__/script-proxy-certs.test.ts +1 -1
  16. package/src/__tests__/subagent-tool-gate-mode.test.ts +0 -169
  17. package/src/__tests__/terminal-tools.test.ts +0 -8
  18. package/src/__tests__/unicode.test.ts +0 -36
  19. package/src/agent/loop.ts +0 -19
  20. package/src/api/index.ts +0 -6
  21. package/src/approvals/approval-primitive.ts +2 -5
  22. package/src/approvals/scoped-approval-grants.ts +2 -6
  23. package/src/daemon/__tests__/conversation-tool-setup.test.ts +0 -43
  24. package/src/daemon/conversation-agent-loop.ts +0 -2
  25. package/src/daemon/conversation-tool-setup.ts +1 -57
  26. package/src/daemon/conversation.ts +0 -17
  27. package/src/daemon/daemon-control.ts +6 -2
  28. package/src/daemon/orphan-reaper.ts +3 -4
  29. package/src/daemon/tool-setup-types.ts +0 -6
  30. package/src/daemon/wake-conversation-ops.ts +15 -38
  31. package/src/desktop/desktop-automation-lease.test.ts +0 -143
  32. package/src/desktop/desktop-automation-lease.ts +3 -39
  33. package/src/messaging/provider-message-metadata.ts +3 -3
  34. package/src/notifications/__tests__/copy-composer.test.ts +0 -70
  35. package/src/notifications/copy-composer.ts +3 -11
  36. package/src/oauth/seed-providers.ts +30 -0
  37. package/src/persistence/conversation-plugin-facade.ts +0 -13
  38. package/src/persistence/schema/index.ts +0 -1
  39. package/src/persistence/steps.ts +0 -2
  40. package/src/plugin-api/conversation-turn.ts +7 -31
  41. package/src/plugin-api/index.ts +1 -9
  42. package/src/plugins/defaults/memory/AGENTS.md +2 -14
  43. package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +0 -43
  44. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +0 -46
  45. package/src/plugins/defaults/memory/buffer-format.ts +0 -40
  46. package/src/plugins/defaults/memory/context-search/agent-runner.ts +2 -1
  47. package/src/plugins/defaults/memory/context-search/format.ts +1 -2
  48. package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +1 -2
  49. package/src/plugins/defaults/memory/context-search/sources/workspace.ts +1 -2
  50. package/src/plugins/defaults/memory/graph/capability-seed.ts +2 -1
  51. package/src/plugins/defaults/memory/graph/tool-handlers.ts +42 -1
  52. package/src/plugins/defaults/memory/host-utils.ts +10 -0
  53. package/src/plugins/defaults/memory/injectors.ts +3 -4
  54. package/src/plugins/defaults/memory/memory-retrospective-job.ts +181 -55
  55. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +99 -407
  56. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +0 -10
  57. package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +7 -107
  58. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +86 -307
  59. package/src/plugins/defaults/memory/substrate/page-index.ts +1 -2
  60. package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +49 -89
  61. package/src/plugins/defaults/memory/substrate/sweep-job.ts +1 -1
  62. package/src/plugins/defaults/memory/tools.ts +1 -1
  63. package/src/plugins/defaults/memory/v1/graph/consolidation.ts +2 -2
  64. package/src/plugins/defaults/memory/v1/graph/extraction.ts +1 -2
  65. package/src/plugins/defaults/memory/v1/graph/retriever.ts +1 -1
  66. package/src/plugins/defaults/memory/v2/__tests__/migration.test.ts +0 -5
  67. package/src/plugins/defaults/memory/v2/__tests__/reranker.test.ts +2 -5
  68. package/src/plugins/defaults/memory/v2/reranker.ts +1 -2
  69. package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +1 -81
  70. package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +0 -87
  71. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +0 -21
  72. package/src/plugins/defaults/memory/v3/card.ts +1 -2
  73. package/src/plugins/defaults/memory/v3/injector.ts +178 -212
  74. package/src/plugins/defaults/memory/v3/orchestrate.ts +22 -86
  75. package/src/plugins/defaults/memory/v3/pool-select.ts +7 -10
  76. package/src/plugins/defaults/memory/v3/sections.ts +1 -2
  77. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +1 -10
  78. package/src/plugins/defaults/tool-result-truncate/terminal.ts +46 -1
  79. package/src/runtime/AGENTS.md +1 -1
  80. package/src/runtime/__tests__/agent-wake.test.ts +1 -86
  81. package/src/runtime/agent-wake.ts +4 -20
  82. package/src/runtime/guardian-action-service.ts +17 -2
  83. package/src/runtime/guardian-reply-router.ts +8 -1
  84. package/src/runtime/routes/channel-route-shared.ts +9 -1
  85. package/src/runtime/routes/desktop-setup-routes.test.ts +2 -2
  86. package/src/runtime/routes/desktop-setup-routes.ts +3 -7
  87. package/src/runtime/routes/guardian-approval-interception.ts +0 -24
  88. package/src/runtime/routes/inbound-message-handler.ts +3 -2
  89. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +1 -1
  90. package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -7
  91. package/src/schedule/run-script.ts +2 -2
  92. package/src/tools/host-terminal/host-shell.ts +6 -12
  93. package/src/tools/shared/filesystem/file-ops-service.ts +31 -1
  94. package/src/tools/shared/shell-output.test.ts +0 -10
  95. package/src/tools/shared/shell-output.ts +2 -14
  96. package/src/tools/skills/sandbox-runner.ts +2 -13
  97. package/src/tools/skills/scaffold-managed.ts +1 -2
  98. package/src/tools/terminal/__tests__/safe-env.test.ts +0 -29
  99. package/src/tools/terminal/safe-env.ts +1 -30
  100. package/src/tools/terminal/sanitized-bash.ts +2 -15
  101. package/src/tools/terminal/shell.test.ts +0 -29
  102. package/src/tools/terminal/shell.ts +7 -13
  103. package/src/util/host-process.test.ts +1 -17
  104. package/src/util/host-process.ts +0 -24
  105. package/src/util/unicode.ts +0 -29
  106. package/src/__tests__/db-conversation-tool-surface.test.ts +0 -144
  107. package/src/api/events/desktop-activity-changed.ts +0 -10
  108. package/src/persistence/conversation-tool-surface.ts +0 -86
  109. package/src/persistence/migrations/378-create-conversation-tool-surfaces.test.ts +0 -78
  110. package/src/persistence/migrations/378-create-conversation-tool-surfaces.ts +0 -29
  111. package/src/persistence/schema/conversation-tool-surfaces.ts +0 -22
  112. package/src/plugin-api/plugin-channel-turn-trust.test.ts +0 -133
  113. package/src/plugin-api/plugin-channel-turn-trust.ts +0 -71
  114. package/src/plugins/defaults/memory/__tests__/buffer-file.test.ts +0 -320
  115. package/src/plugins/defaults/memory/__tests__/fixtures/buffer-appender.ts +0 -17
  116. package/src/plugins/defaults/memory/__tests__/memory-run-evidence.test.ts +0 -161
  117. package/src/plugins/defaults/memory/buffer-file.ts +0 -354
  118. package/src/plugins/defaults/memory/memory-run-evidence.ts +0 -213
  119. package/src/plugins/defaults/memory/substrate/consolidation-tool-surface.ts +0 -34
  120. package/src/tools/terminal/shell-launch.test.ts +0 -162
@@ -1,86 +0,0 @@
1
- import { createHash } from "node:crypto";
2
-
3
- import { eq, sql } from "drizzle-orm";
4
-
5
- import type { ToolDefinition } from "../providers/types.js";
6
- import { getDb } from "./db-connection.js";
7
- import { conversationToolSurfaces } from "./schema/index.js";
8
-
9
- /**
10
- * Per-conversation record of the tool definitions the most recent live turn
11
- * sent to the provider (`conversation_tool_surfaces`).
12
- *
13
- * The provider prompt cache is a byte-exact prefix match over
14
- * `tools -> system -> messages`, so a background wake that forks a
15
- * conversation can only reuse the source's cached prefix by sending the SAME
16
- * tools array. Re-deriving the array on the fork cannot guarantee that: the
17
- * fork may run in another process (a different tool registry), with no
18
- * connected clients (different host-tool gates), or under a presence the
19
- * persisted message stamps do not encode. Recording the resolved array and
20
- * replaying it verbatim can.
21
- */
22
-
23
- /** Content hash of the serialized tools array. */
24
- export function hashConversationToolSurface(
25
- tools: readonly ToolDefinition[],
26
- ): string {
27
- return hashToolsJson(JSON.stringify(tools));
28
- }
29
-
30
- function hashToolsJson(toolsJson: string): string {
31
- return createHash("sha256").update(toolsJson).digest("hex").slice(0, 32);
32
- }
33
-
34
- /**
35
- * Persist `tools` as the conversation's current wire tool surface and return
36
- * its hash. `knownHash` is the hash the caller last recorded for this
37
- * conversation (undefined when it has recorded nothing this process
38
- * lifetime): a matching hash skips the statement outright. Otherwise the
39
- * upsert only rewrites a row whose stored hash differs, so a conversation
40
- * reloaded from disk never rewrites an unchanged surface either.
41
- */
42
- export function recordConversationToolSurface(
43
- conversationId: string,
44
- tools: readonly ToolDefinition[],
45
- knownHash?: string,
46
- ): string {
47
- const toolsJson = JSON.stringify(tools);
48
- const toolsHash = hashToolsJson(toolsJson);
49
- if (knownHash === toolsHash) {
50
- return toolsHash;
51
- }
52
- const updatedAt = Date.now();
53
- getDb()
54
- .insert(conversationToolSurfaces)
55
- .values({ conversationId, toolsJson, toolsHash, updatedAt })
56
- .onConflictDoUpdate({
57
- target: conversationToolSurfaces.conversationId,
58
- set: { toolsJson, toolsHash, updatedAt },
59
- setWhere: sql`${conversationToolSurfaces.toolsHash} <> excluded.tools_hash`,
60
- })
61
- .run();
62
- return toolsHash;
63
- }
64
-
65
- /**
66
- * The tool definitions the conversation's most recent live turn sent, or
67
- * `null` when no turn has recorded one (or the stored JSON is unreadable).
68
- */
69
- export function getConversationToolSurface(
70
- conversationId: string,
71
- ): ToolDefinition[] | null {
72
- const row = getDb()
73
- .select({ toolsJson: conversationToolSurfaces.toolsJson })
74
- .from(conversationToolSurfaces)
75
- .where(eq(conversationToolSurfaces.conversationId, conversationId))
76
- .get();
77
- if (!row) {
78
- return null;
79
- }
80
- try {
81
- const parsed: unknown = JSON.parse(row.toolsJson);
82
- return Array.isArray(parsed) ? (parsed as ToolDefinition[]) : null;
83
- } catch {
84
- return null;
85
- }
86
- }
@@ -1,78 +0,0 @@
1
- import { Database } from "bun:sqlite";
2
- import { describe, expect, test } from "bun:test";
3
-
4
- import { drizzle } from "drizzle-orm/bun-sqlite";
5
-
6
- import * as schema from "../schema.js";
7
- import { migrateCreateConversationToolSurfaces } from "./378-create-conversation-tool-surfaces.js";
8
-
9
- function createTestDb() {
10
- const sqlite = new Database(":memory:");
11
- sqlite.run("PRAGMA foreign_keys = ON");
12
- // Only the parent table the FK points at.
13
- sqlite.run(/*sql*/ `CREATE TABLE conversations (id TEXT PRIMARY KEY)`);
14
- return { sqlite, db: drizzle(sqlite, { schema }) };
15
- }
16
-
17
- function tableDdl(sqlite: Database): string | undefined {
18
- const row = sqlite
19
- .query(
20
- `SELECT sql FROM sqlite_master WHERE type='table' AND name='conversation_tool_surfaces'`,
21
- )
22
- .get() as { sql: string } | null;
23
- return row?.sql;
24
- }
25
-
26
- function insertSurface(sqlite: Database, conversationId: string): void {
27
- sqlite
28
- .query(
29
- /*sql*/ `INSERT INTO conversation_tool_surfaces (conversation_id, tools_json, tools_hash, updated_at) VALUES (?, ?, ?, ?)`,
30
- )
31
- .run(conversationId, "[]", "hash", 1);
32
- }
33
-
34
- function surfaceCount(sqlite: Database): number {
35
- const row = sqlite
36
- .query(`SELECT COUNT(*) AS n FROM conversation_tool_surfaces`)
37
- .get() as { n: number };
38
- return row.n;
39
- }
40
-
41
- describe("migration 378: conversation_tool_surfaces", () => {
42
- test("creates the table keyed by conversation with a cascading FK", () => {
43
- const { sqlite, db } = createTestDb();
44
- expect(tableDdl(sqlite)).toBeUndefined();
45
-
46
- migrateCreateConversationToolSurfaces(db);
47
-
48
- const ddl = tableDdl(sqlite);
49
- expect(ddl).toContain("conversation_id TEXT PRIMARY KEY");
50
- expect(ddl).toContain("REFERENCES conversations(id) ON DELETE CASCADE");
51
- expect(ddl).toContain("tools_json TEXT NOT NULL");
52
- expect(ddl).toContain("tools_hash TEXT NOT NULL");
53
- expect(ddl).toContain("updated_at INTEGER NOT NULL");
54
- });
55
-
56
- test("deleting a conversation cascades to its surface row", () => {
57
- const { sqlite, db } = createTestDb();
58
- migrateCreateConversationToolSurfaces(db);
59
- sqlite.query(`INSERT INTO conversations (id) VALUES (?)`).run("conv-1");
60
- insertSurface(sqlite, "conv-1");
61
- expect(surfaceCount(sqlite)).toBe(1);
62
-
63
- sqlite.query(`DELETE FROM conversations WHERE id = ?`).run("conv-1");
64
-
65
- expect(surfaceCount(sqlite)).toBe(0);
66
- });
67
-
68
- test("is idempotent: a second run keeps the table and its rows", () => {
69
- const { sqlite, db } = createTestDb();
70
- migrateCreateConversationToolSurfaces(db);
71
- sqlite.query(`INSERT INTO conversations (id) VALUES (?)`).run("conv-1");
72
- insertSurface(sqlite, "conv-1");
73
-
74
- expect(() => migrateCreateConversationToolSurfaces(db)).not.toThrow();
75
-
76
- expect(surfaceCount(sqlite)).toBe(1);
77
- });
78
- });
@@ -1,29 +0,0 @@
1
- import type { DrizzleDb } from "../db-connection.js";
2
-
3
- const TABLE = "conversation_tool_surfaces";
4
-
5
- /**
6
- * Create `conversation_tool_surfaces`: one row per conversation holding the
7
- * tool definitions its most recent live turn sent to the provider, keyed by
8
- * conversation with a content hash so an unchanged surface is never rewritten.
9
- *
10
- * Its own table rather than a column on `conversations` because the payload
11
- * is a JSON array of every tool definition (tens of kilobytes) that only fork
12
- * wakes read, while `conversations` rows are read whole on every hot path.
13
- * The row cascades with its conversation, so a deleted conversation leaves
14
- * no surface behind.
15
- *
16
- * Idempotent via IF NOT EXISTS.
17
- */
18
- export function migrateCreateConversationToolSurfaces(
19
- database: DrizzleDb,
20
- ): void {
21
- database.run(
22
- `CREATE TABLE IF NOT EXISTS ${TABLE} (
23
- conversation_id TEXT PRIMARY KEY REFERENCES conversations(id) ON DELETE CASCADE,
24
- tools_json TEXT NOT NULL,
25
- tools_hash TEXT NOT NULL,
26
- updated_at INTEGER NOT NULL
27
- )`,
28
- );
29
- }
@@ -1,22 +0,0 @@
1
- import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
2
-
3
- import { conversations } from "./conversations.js";
4
-
5
- /**
6
- * The tool definitions a conversation's most recent live turn sent to the
7
- * provider, serialized exactly as resolved. Written by the per-turn tool
8
- * resolver when the array changes; read by fork wakes that replay it so their
9
- * provider prompt-cache prefix matches the source's live turns. Cascades with
10
- * its conversation.
11
- */
12
- export const conversationToolSurfaces = sqliteTable(
13
- "conversation_tool_surfaces",
14
- {
15
- conversationId: text("conversation_id")
16
- .primaryKey()
17
- .references(() => conversations.id, { onDelete: "cascade" }),
18
- toolsJson: text("tools_json").notNull(),
19
- toolsHash: text("tools_hash").notNull(),
20
- updatedAt: integer("updated_at").notNull(),
21
- },
22
- );
@@ -1,133 +0,0 @@
1
- import { beforeEach, describe, expect, mock, test } from "bun:test";
2
-
3
- type InboundTrustReadResult =
4
- | {
5
- ok: true;
6
- verdict: {
7
- trustClass: string;
8
- canonicalSenderId: string | null;
9
- contactId?: string;
10
- status?: string;
11
- resolutionFailed?: boolean;
12
- };
13
- admissionPolicy: string | null;
14
- }
15
- | { ok: false };
16
-
17
- let nextRead: InboundTrustReadResult = { ok: false };
18
-
19
- mock.module("../calls/inbound-trust-reader.js", () => ({
20
- readInboundTrust: async () => nextRead,
21
- }));
22
-
23
- import {
24
- PluginTurnNotAdmittedError,
25
- resolvePluginChannelTurnTrust,
26
- } from "./plugin-channel-turn-trust.js";
27
-
28
- const CHANNEL = {
29
- sourceChannel: "plugin" as const,
30
- externalChatId: "imessage:+12025550142",
31
- externalUserId: "imessage:+12025550142",
32
- displayName: "Ada",
33
- };
34
-
35
- beforeEach(() => {
36
- nextRead = { ok: false };
37
- });
38
-
39
- describe("resolvePluginChannelTurnTrust", () => {
40
- test("returns the gateway verdict as the turn trust context", async () => {
41
- nextRead = {
42
- ok: true,
43
- verdict: {
44
- trustClass: "trusted_contact",
45
- canonicalSenderId: "imessage:+12025550142",
46
- contactId: "c-ada",
47
- status: "active",
48
- },
49
- admissionPolicy: "trusted_contacts",
50
- };
51
-
52
- const trust = await resolvePluginChannelTurnTrust(CHANNEL);
53
- expect(trust.trustClass).toBe("trusted_contact");
54
- expect(trust.sourceChannel).toBe("plugin");
55
- expect(trust.requesterChatId).toBe(CHANNEL.externalChatId);
56
- });
57
-
58
- test("fails closed when the gateway could not vouch for the sender", async () => {
59
- nextRead = {
60
- ok: true,
61
- verdict: {
62
- trustClass: "unknown",
63
- canonicalSenderId: null,
64
- resolutionFailed: true,
65
- },
66
- admissionPolicy: "strangers",
67
- };
68
-
69
- await expect(resolvePluginChannelTurnTrust(CHANNEL)).rejects.toMatchObject({
70
- name: "PluginTurnNotAdmittedError",
71
- reason: "trust_resolution_failed",
72
- });
73
- });
74
-
75
- test("fails closed when the gateway trust read fails", async () => {
76
- nextRead = { ok: false };
77
- await expect(resolvePluginChannelTurnTrust(CHANNEL)).rejects.toMatchObject({
78
- name: "PluginTurnNotAdmittedError",
79
- reason: "trust_resolution_failed",
80
- });
81
- });
82
-
83
- test("rejects a sender below the channel admission floor", async () => {
84
- nextRead = {
85
- ok: true,
86
- verdict: { trustClass: "unknown", canonicalSenderId: null },
87
- admissionPolicy: "guardian_only",
88
- };
89
-
90
- await expect(resolvePluginChannelTurnTrust(CHANNEL)).rejects.toEqual(
91
- expect.objectContaining({
92
- name: "PluginTurnNotAdmittedError",
93
- reason: "admission_policy_guardian_only",
94
- }),
95
- );
96
- });
97
-
98
- test("rejects a blocked member regardless of floor", async () => {
99
- nextRead = {
100
- ok: true,
101
- verdict: {
102
- trustClass: "trusted_contact",
103
- canonicalSenderId: "imessage:+12025550142",
104
- contactId: "c-ada",
105
- status: "blocked",
106
- },
107
- admissionPolicy: "strangers",
108
- };
109
-
110
- await expect(resolvePluginChannelTurnTrust(CHANNEL)).rejects.toMatchObject({
111
- name: "PluginTurnNotAdmittedError",
112
- reason: "member_blocked",
113
- });
114
- });
115
-
116
- test("admits when the gateway reports no admission policy", async () => {
117
- nextRead = {
118
- ok: true,
119
- verdict: { trustClass: "unknown", canonicalSenderId: null },
120
- admissionPolicy: null,
121
- };
122
-
123
- const trust = await resolvePluginChannelTurnTrust(CHANNEL);
124
- expect(trust.trustClass).toBe("unknown");
125
- });
126
-
127
- test("PluginTurnNotAdmittedError names the deny reason", () => {
128
- const err = new PluginTurnNotAdmittedError("admission_policy_no_one");
129
- expect(err).toBeInstanceOf(Error);
130
- expect(err.reason).toBe("admission_policy_no_one");
131
- expect(err.message).toContain("admission_policy_no_one");
132
- });
133
- });
@@ -1,71 +0,0 @@
1
- /**
2
- * Gateway admission and trust for plugin turns addressed by a channel chat.
3
- *
4
- * A channel address means an inbound sender, not an internal plugin job.
5
- * Trust comes from the gateway verdict (`resolve_inbound_trust`); the turn
6
- * runs only when that sender clears the channel admission floor.
7
- */
8
-
9
- import { meetsAdmissionFloor } from "@vellumai/gateway-client";
10
-
11
- import { readInboundTrust } from "../calls/inbound-trust-reader.js";
12
- import type { ChannelId } from "../channels/types.js";
13
- import type { TrustContext } from "../daemon/trust-context-types.js";
14
- import { trustContextFromVerdict } from "../runtime/trust-verdict-consumer.js";
15
-
16
- interface PluginChannelTurnAddress {
17
- sourceChannel: ChannelId;
18
- externalChatId: string;
19
- externalUserId?: string | null;
20
- displayName?: string | null;
21
- username?: string | null;
22
- }
23
-
24
- export class PluginTurnNotAdmittedError extends Error {
25
- readonly reason: string;
26
-
27
- constructor(reason: string) {
28
- super(`This inbound plugin turn was not admitted (${reason}).`);
29
- this.name = "PluginTurnNotAdmittedError";
30
- this.reason = reason;
31
- }
32
- }
33
-
34
- export async function resolvePluginChannelTurnTrust(
35
- channel: PluginChannelTurnAddress,
36
- ): Promise<TrustContext> {
37
- const actorExternalId = channel.externalUserId?.trim() || undefined;
38
- const read = await readInboundTrust({
39
- channelType: channel.sourceChannel,
40
- actorExternalId,
41
- });
42
- if (!read.ok) {
43
- throw new PluginTurnNotAdmittedError("trust_resolution_failed");
44
- }
45
-
46
- if (read.verdict.resolutionFailed) {
47
- throw new PluginTurnNotAdmittedError("trust_resolution_failed");
48
- }
49
-
50
- const memberStatus = read.verdict.status;
51
- if (memberStatus === "blocked" || memberStatus === "revoked") {
52
- throw new PluginTurnNotAdmittedError(`member_${memberStatus}`);
53
- }
54
-
55
- const trustClass = read.verdict.trustClass;
56
- if (
57
- read.admissionPolicy != null &&
58
- !meetsAdmissionFloor(read.admissionPolicy, trustClass)
59
- ) {
60
- throw new PluginTurnNotAdmittedError(
61
- `admission_policy_${read.admissionPolicy}`,
62
- );
63
- }
64
-
65
- return trustContextFromVerdict(read.verdict, {
66
- sourceChannel: channel.sourceChannel,
67
- conversationExternalId: channel.externalChatId,
68
- actorDisplayName: channel.displayName ?? undefined,
69
- actorUsername: channel.username ?? undefined,
70
- });
71
- }