@loggie-ai/openclaw-plugin 0.1.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.
Files changed (55) hide show
  1. package/README.md +205 -0
  2. package/dist/index.d.ts +8 -0
  3. package/dist/index.js +12 -0
  4. package/dist/setup-entry.d.ts +73 -0
  5. package/dist/setup-entry.js +3 -0
  6. package/dist/src/account.d.ts +70 -0
  7. package/dist/src/account.js +182 -0
  8. package/dist/src/channel.d.ts +72 -0
  9. package/dist/src/channel.js +105 -0
  10. package/dist/src/config-schema.d.ts +98 -0
  11. package/dist/src/config-schema.js +55 -0
  12. package/dist/src/cursor-store.d.ts +86 -0
  13. package/dist/src/cursor-store.js +141 -0
  14. package/dist/src/doctor.d.ts +11 -0
  15. package/dist/src/doctor.js +29 -0
  16. package/dist/src/event-types.d.ts +113 -0
  17. package/dist/src/event-types.js +86 -0
  18. package/dist/src/loggie-client.d.ts +33 -0
  19. package/dist/src/loggie-client.js +74 -0
  20. package/dist/src/monitor/connection.d.ts +30 -0
  21. package/dist/src/monitor/connection.js +289 -0
  22. package/dist/src/monitor/event-handler.d.ts +27 -0
  23. package/dist/src/monitor/event-handler.js +90 -0
  24. package/dist/src/monitor/transcript-dispatch.d.ts +16 -0
  25. package/dist/src/monitor/transcript-dispatch.js +124 -0
  26. package/dist/src/monitor/transcript-format.d.ts +2 -0
  27. package/dist/src/monitor/transcript-format.js +41 -0
  28. package/dist/src/object.d.ts +4 -0
  29. package/dist/src/object.js +12 -0
  30. package/dist/src/routing.d.ts +11 -0
  31. package/dist/src/routing.js +13 -0
  32. package/dist/src/runtime.d.ts +3 -0
  33. package/dist/src/runtime.js +6 -0
  34. package/dist/src/status.d.ts +45 -0
  35. package/dist/src/status.js +45 -0
  36. package/index.ts +13 -0
  37. package/openclaw.plugin.json +71 -0
  38. package/package.json +93 -0
  39. package/plugin-inspector.config.json +15 -0
  40. package/setup-entry.ts +4 -0
  41. package/src/account.ts +265 -0
  42. package/src/channel.ts +148 -0
  43. package/src/config-schema.ts +57 -0
  44. package/src/cursor-store.ts +233 -0
  45. package/src/doctor.ts +39 -0
  46. package/src/event-types.ts +105 -0
  47. package/src/loggie-client.ts +111 -0
  48. package/src/monitor/connection.ts +349 -0
  49. package/src/monitor/event-handler.ts +133 -0
  50. package/src/monitor/transcript-dispatch.ts +145 -0
  51. package/src/monitor/transcript-format.ts +49 -0
  52. package/src/object.ts +15 -0
  53. package/src/routing.ts +27 -0
  54. package/src/runtime.ts +13 -0
  55. package/src/status.ts +72 -0
package/README.md ADDED
@@ -0,0 +1,205 @@
1
+ # Loggie OpenClaw Plugin
2
+
3
+ OpenClaw channel plugin for Loggie meeting transcript events.
4
+
5
+ The plugin opens an outbound authenticated WebSocket to Loggie, replays from the
6
+ last durable cursor, and turns each `meeting.transcript.ready` event into one
7
+ OpenClaw inbound channel turn for the configured agent.
8
+
9
+ ## How It Works
10
+
11
+ 1. OpenClaw loads the setup-safe entrypoint for discovery, config inspection,
12
+ status, doctor checks, and SecretRef target discovery.
13
+ 2. When the configured channel account starts, the full runtime entrypoint lazy
14
+ loads the WebSocket monitor.
15
+ 3. The monitor opens a WebSocket to `baseUrl + socketPath`, authenticating with
16
+ either `Authorization: Bearer <token>` or `x-api-key: <token>`.
17
+ 4. Before opening the socket, the plugin loads the durable cursor from OpenClaw
18
+ runtime state and appends it to the URL as `?cursor=<lastCursor>`.
19
+ 5. Loggie sends event envelopes. The plugin validates the envelope, skips
20
+ duplicates or cursor regressions, and processes `meeting.transcript.ready`.
21
+ 6. If the event includes `payload.detailPath`, the plugin fetches that detail
22
+ from the same Loggie origin with the same auth header. Cross-origin detail
23
+ URLs are rejected before auth headers are attached.
24
+ 7. The plugin builds a deterministic OpenClaw session key from the agent,
25
+ account, and meeting id, records the inbound route, and runs the configured
26
+ OpenClaw agent with the transcript prompt.
27
+ 8. The cursor advances only after dispatch succeeds, or after an unsupported
28
+ event is intentionally skipped.
29
+
30
+ ## Loggie Server Contract
31
+
32
+ Minimum server-side behavior expected by this plugin:
33
+
34
+ - Authenticated WebSocket endpoint at `/api/events/socket` by default.
35
+ - WebSocket auth using the configured agent profile token.
36
+ - Initial replay from the WebSocket URL query: `?cursor=<cursor>`.
37
+ - Durable event ordering using `eventId` and monotonic string `cursor`.
38
+ - Client messages `{ "type": "ping" }` and `{ "type": "resume", "cursor": "..." }`.
39
+ - Replay pagination control messages:
40
+ `{ "type": "loggie.events.more_available", "cursor": "..." }`.
41
+ - `meeting.transcript.ready` event envelopes.
42
+ - Optional HTTP detail endpoint referenced by `payload.detailPath`.
43
+ - Control messages with `type: "hello"`, `"ack"`, or `"pong"` are accepted and
44
+ treated as connection/control traffic.
45
+
46
+ Example event:
47
+
48
+ ```json
49
+ {
50
+ "cursor": "123",
51
+ "type": "meeting.transcript.ready",
52
+ "payload": {
53
+ "eventId": "evt_1",
54
+ "cursor": "123",
55
+ "type": "meeting.transcript.ready",
56
+ "workspaceId": "ws_1",
57
+ "meetingId": "mtg_1",
58
+ "meetingScheduleId": "sched_1",
59
+ "title": "Weekly Standup",
60
+ "source": "recall_ai",
61
+ "externalId": "tr_1",
62
+ "meetingDate": "2026-06-26T11:30:00.000Z",
63
+ "hasTranscript": true,
64
+ "detailPath": "/api/proxy/meetings/sched_1"
65
+ },
66
+ "createdAt": "2026-06-26T12:00:00.000Z"
67
+ }
68
+ ```
69
+
70
+ The plugin also accepts the older internal envelope shape and `{ "event": { ... } }`
71
+ wrappers for compatibility, but the server socket shape above is the primary
72
+ contract.
73
+
74
+ ## Configuration
75
+
76
+ Configure the channel under `channels.loggie`.
77
+
78
+ ```json
79
+ {
80
+ "channels": {
81
+ "loggie": {
82
+ "enabled": true,
83
+ "baseUrl": "https://loggie.example.com",
84
+ "agentProfileId": "ap_123",
85
+ "credentialRef": "$LOGGIE_AGENT_TOKEN",
86
+ "agentId": "main"
87
+ }
88
+ }
89
+ }
90
+ ```
91
+
92
+ Important fields:
93
+
94
+ - `baseUrl`: Loggie HTTP origin. Defaults to `LOGGIE_BASE_URL`, then
95
+ `http://127.0.0.1:8787`.
96
+ - `socketPath`: WebSocket path. Defaults to `/api/events/socket`.
97
+ - `agentProfileId`: Loggie agent profile used for socket auth and OpenClaw
98
+ sender/account identity. Server events are workspace-scoped and do not need to
99
+ include this field.
100
+ - `credentialRef`: Token input. Use `$ENV_VAR` for env-backed credentials or a
101
+ structured SecretRef. If OpenClaw materializes a SecretRef before runtime, the
102
+ resulting string is treated as token material.
103
+ - `token`: Literal token fallback for local development.
104
+ - `authHeader`: `authorization` by default, or `x-api-key`.
105
+ - `agentId`: OpenClaw agent id that should receive transcript turns. Defaults to
106
+ `main`.
107
+ - `reconnect.minMs` and `reconnect.maxMs`: exponential reconnect backoff bounds.
108
+ - `heartbeat.timeoutMs`: stale socket timeout. Defaults to `120000`.
109
+
110
+ Environment defaults:
111
+
112
+ ```bash
113
+ export LOGGIE_BASE_URL="https://loggie.example.com"
114
+ export LOGGIE_AGENT_TOKEN="..."
115
+ ```
116
+
117
+ For multiple accounts, put account-specific settings under
118
+ `channels.loggie.accounts`:
119
+
120
+ ```json
121
+ {
122
+ "channels": {
123
+ "loggie": {
124
+ "accountId": "prod",
125
+ "accounts": {
126
+ "prod": {
127
+ "baseUrl": "https://loggie.example.com",
128
+ "agentProfileId": "ap_prod",
129
+ "credentialRef": "$LOGGIE_AGENT_TOKEN",
130
+ "agentId": "main"
131
+ }
132
+ }
133
+ }
134
+ }
135
+ }
136
+ ```
137
+
138
+ ## Sessions And Cursoring
139
+
140
+ Session identity is deterministic:
141
+
142
+ - Route session key:
143
+ `agent:<agentId>:loggie:<accountId>:meeting:<meetingScheduleId|meetingId|eventId>`
144
+ - Embedded run session id:
145
+ `loggie-<accountId>-<meetingScheduleId|meetingId|eventId>`
146
+
147
+ The cursor store is required to be durable. Runtime startup fails if OpenClaw
148
+ does not provide a keyed runtime state store. This prevents silent in-memory
149
+ cursor loss and duplicate transcript dispatch after restart.
150
+
151
+ The plugin keeps a bounded set of recently seen event ids and skips:
152
+
153
+ - duplicate `eventId`
154
+ - lower numeric `cursor` values than the stored cursor
155
+
156
+ If transcript dispatch fails for the same event, the plugin records the failure
157
+ in the durable cursor state and retries it on replay. After 3 failed dispatch
158
+ attempts, the event is recorded in `deadLetteredEvents`, the cursor advances,
159
+ and later events can continue processing. The latest dead-letter is also
160
+ reported in channel status as `lastDeadLetterAt`, `lastDeadLetteredEventId`,
161
+ `lastDeadLetteredCursor`, `lastDeadLetterReason`, and
162
+ `lastDeadLetterAttempts`, and the monitor emits a warning log.
163
+
164
+ ## Reconnect And Heartbeat Behavior
165
+
166
+ Unexpected socket errors or closes reconnect with exponential backoff. The
167
+ next socket URL includes the last persisted cursor so Loggie can replay missed
168
+ events. If Loggie reports `loggie.events.more_available`, the plugin sends
169
+ `{ "type": "resume", "cursor": "..." }` to request the next replay page.
170
+
171
+ The plugin sends periodic `{ "type": "ping" }` messages. The heartbeat timeout is
172
+ based on any received socket message, including `pong` and other control
173
+ messages. When the heartbeat expires, the plugin closes the socket and waits for
174
+ in-flight event handling to finish before reconnecting. That avoids dispatching
175
+ the same transcript twice while an earlier turn is still advancing the cursor.
176
+
177
+ ## Security Notes
178
+
179
+ - Tokens are used only for outbound Loggie auth headers.
180
+ - Status and inspect output report token availability/source, not token values.
181
+ - Detail fetches are restricted to the configured Loggie origin.
182
+ - `credentialRef` should be `$ENV_VAR` or a structured SecretRef for config.
183
+ Plain strings are treated as materialized token values.
184
+
185
+ ## Development
186
+
187
+ ```bash
188
+ npm install
189
+ npm run check
190
+ npm test
191
+ npm run build
192
+ npm run preflight
193
+ ```
194
+
195
+ Useful targeted commands:
196
+
197
+ ```bash
198
+ npm run plugin:inspect
199
+ npm run plugin:inspect:runtime
200
+ npx tsc -p tsconfig.json --noEmit --noUnusedLocals --noUnusedParameters
201
+ npm pack --dry-run
202
+ ```
203
+
204
+ `npm run preflight` runs typecheck, tests, build, and both plugin-inspector
205
+ passes.
@@ -0,0 +1,8 @@
1
+ declare const _default: {
2
+ id: string;
3
+ name: string;
4
+ description: string;
5
+ channelPlugin: unknown;
6
+ register(api: Record<string, unknown>): void;
7
+ };
8
+ export default _default;
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core";
2
+ import { loggiePlugin } from "./src/channel.js";
3
+ import { loggieEntryConfigSchema } from "./src/config-schema.js";
4
+ import { setLoggieRuntime } from "./src/runtime.js";
5
+ export default defineChannelPluginEntry({
6
+ id: "loggie",
7
+ name: "Loggie",
8
+ description: "Receives durable Loggie meeting transcript events over WebSocket.",
9
+ plugin: loggiePlugin,
10
+ configSchema: loggieEntryConfigSchema,
11
+ setRuntime: setLoggieRuntime,
12
+ });
@@ -0,0 +1,73 @@
1
+ declare const _default: {
2
+ plugin: {
3
+ status: {
4
+ defaultRuntime: {
5
+ accountId: string;
6
+ running: false;
7
+ lastStartAt: null;
8
+ lastStopAt: null;
9
+ lastError: null;
10
+ } & import("./src/status.js").LoggieRuntimeExtra;
11
+ buildAccountSnapshot: ({ account, runtime, }: {
12
+ account: import("./src/account.js").ResolvedLoggieAccount;
13
+ runtime?: Record<string, unknown> | null;
14
+ }) => Record<string, unknown> & {
15
+ mode: string;
16
+ baseUrl: string;
17
+ agentId: string;
18
+ agentProfileId: string;
19
+ tokenStatus: "available" | "configured_unavailable" | "missing";
20
+ tokenSource: string;
21
+ connected: {};
22
+ authenticated: {};
23
+ caughtUp: {};
24
+ lastEventId: {} | null;
25
+ lastCursor: {} | null;
26
+ lastSequence: {} | null;
27
+ lastDispatchAt: {} | null;
28
+ lastDeadLetterAt: {} | null;
29
+ lastDeadLetteredEventId: {} | null;
30
+ lastDeadLetteredCursor: {} | null;
31
+ lastDeadLetterReason: {} | null;
32
+ lastDeadLetterAttempts: {} | null;
33
+ };
34
+ };
35
+ doctor: {
36
+ collectPreviewWarnings: (params: {
37
+ cfg: import("openclaw/plugin-sdk/channel-core").OpenClawConfig;
38
+ env?: NodeJS.ProcessEnv;
39
+ }) => string[];
40
+ };
41
+ secrets: {
42
+ secretTargetRegistryEntries: {
43
+ id: string;
44
+ targetType: string;
45
+ configFile: string;
46
+ pathPattern: string;
47
+ secretShape: string;
48
+ expectedResolvedValue: string;
49
+ includeInPlan: boolean;
50
+ includeInConfigure: boolean;
51
+ includeInAudit: boolean;
52
+ }[];
53
+ };
54
+ gateway: {
55
+ startAccount: (ctx: {
56
+ account: import("./src/account.js").ResolvedLoggieAccount;
57
+ cfg: import("openclaw/plugin-sdk/channel-core").OpenClawConfig;
58
+ runtime: unknown;
59
+ channelRuntime?: unknown;
60
+ abortSignal?: AbortSignal;
61
+ setStatus: (next: unknown) => void;
62
+ getStatus: () => unknown;
63
+ log?: {
64
+ info?: (message: string) => void;
65
+ warn?: (message: string) => void;
66
+ };
67
+ }) => Promise<void>;
68
+ };
69
+ id: string;
70
+ config?: Record<string, unknown>;
71
+ };
72
+ };
73
+ export default _default;
@@ -0,0 +1,3 @@
1
+ import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core";
2
+ import { loggiePlugin } from "./src/channel.js";
3
+ export default defineSetupPluginEntry(loggiePlugin);
@@ -0,0 +1,70 @@
1
+ import type { OpenClawConfig } from "openclaw/plugin-sdk/channel-core";
2
+ export type LoggieAuthHeader = "authorization" | "x-api-key";
3
+ export type LoggieSecretStatus = {
4
+ status: "available";
5
+ source: string;
6
+ value: string;
7
+ } | {
8
+ status: "configured_unavailable";
9
+ source: string;
10
+ value?: undefined;
11
+ } | {
12
+ status: "missing";
13
+ source: "missing";
14
+ value?: undefined;
15
+ };
16
+ export type ResolvedLoggieAccount = {
17
+ accountId: string;
18
+ name?: string;
19
+ enabled: boolean;
20
+ configured: boolean;
21
+ baseUrl: string;
22
+ socketPath: string;
23
+ agentId: string;
24
+ agentProfileId: string;
25
+ authHeader: LoggieAuthHeader;
26
+ tokenStatus: LoggieSecretStatus;
27
+ reconnect: {
28
+ minMs: number;
29
+ maxMs: number;
30
+ };
31
+ heartbeat: {
32
+ timeoutMs: number;
33
+ };
34
+ transcript: {
35
+ activation: "final-only";
36
+ debounceMs: number;
37
+ };
38
+ };
39
+ export declare function listLoggieAccountIds(cfg: OpenClawConfig): string[];
40
+ export declare function resolveDefaultLoggieAccountId(cfg: OpenClawConfig): string;
41
+ export declare function resolveLoggieAccount(cfg: OpenClawConfig, accountId?: string | null, env?: NodeJS.ProcessEnv): ResolvedLoggieAccount;
42
+ export declare function inspectLoggieAccount(cfg: OpenClawConfig, accountId?: string | null): {
43
+ accountId: string;
44
+ name: string | undefined;
45
+ enabled: boolean;
46
+ configured: boolean;
47
+ baseUrl: string;
48
+ socketPath: string;
49
+ agentId: string;
50
+ agentProfileId: string;
51
+ authHeader: LoggieAuthHeader;
52
+ tokenStatus: "available" | "configured_unavailable" | "missing";
53
+ tokenSource: string;
54
+ reconnect: {
55
+ minMs: number;
56
+ maxMs: number;
57
+ };
58
+ heartbeat: {
59
+ timeoutMs: number;
60
+ };
61
+ transcript: {
62
+ activation: "final-only";
63
+ debounceMs: number;
64
+ };
65
+ };
66
+ export declare function applyLoggieAccountConfig(params: {
67
+ cfg: OpenClawConfig;
68
+ accountId: string;
69
+ input: Record<string, unknown>;
70
+ }): OpenClawConfig;
@@ -0,0 +1,182 @@
1
+ import { DEFAULT_LOGGIE_ACCOUNT_ID, DEFAULT_LOGGIE_AGENT_ID, DEFAULT_LOGGIE_BASE_URL_ENV, DEFAULT_LOGGIE_HEARTBEAT_TIMEOUT_MS, DEFAULT_LOGGIE_SOCKET_PATH, DEFAULT_LOGGIE_TOKEN_ENV, } from "./config-schema.js";
2
+ import { isRecord, readBoolean, readInteger, readString } from "./object.js";
3
+ function getLoggieSection(cfg) {
4
+ const channels = isRecord(cfg.channels)
5
+ ? cfg.channels
6
+ : {};
7
+ return isRecord(channels.loggie) ? channels.loggie : {};
8
+ }
9
+ function getAccounts(section) {
10
+ if (!isRecord(section.accounts)) {
11
+ return {};
12
+ }
13
+ const entries = {};
14
+ for (const [id, raw] of Object.entries(section.accounts)) {
15
+ if (isRecord(raw)) {
16
+ entries[id] = raw;
17
+ }
18
+ }
19
+ return entries;
20
+ }
21
+ function mergeAccountConfig(section, accountId) {
22
+ const { accounts: _accounts, ...topLevel } = section;
23
+ return {
24
+ ...topLevel,
25
+ ...(getAccounts(section)[accountId] ?? {}),
26
+ };
27
+ }
28
+ function normalizeAuthHeader(value) {
29
+ return value === "x-api-key" ? "x-api-key" : "authorization";
30
+ }
31
+ function readEnvSecretRefName(value) {
32
+ if (typeof value === "string") {
33
+ const trimmed = value.trim();
34
+ if (trimmed.length === 0) {
35
+ return undefined;
36
+ }
37
+ const envName = trimmed.startsWith("$")
38
+ ? trimmed.replace(/^\$\{?([^}]+)\}?$/, "$1")
39
+ : undefined;
40
+ return envName && /^[A-Z][A-Z0-9_]{0,127}$/.test(envName) ? envName : undefined;
41
+ }
42
+ if (isRecord(value) &&
43
+ value.source === "env" &&
44
+ typeof value.id === "string" &&
45
+ value.id.trim().length > 0) {
46
+ return value.id.trim();
47
+ }
48
+ return undefined;
49
+ }
50
+ function readSecretRefLabel(value) {
51
+ if (isRecord(value) &&
52
+ typeof value.source === "string" &&
53
+ typeof value.provider === "string" &&
54
+ typeof value.id === "string") {
55
+ return `${value.source}:${value.provider}:${value.id}`;
56
+ }
57
+ return undefined;
58
+ }
59
+ function resolveToken(params) {
60
+ const literalToken = readString(params.raw.token);
61
+ if (literalToken) {
62
+ return { status: "available", source: "literal", value: literalToken };
63
+ }
64
+ if (params.raw.credentialRef === undefined) {
65
+ const value = readString(params.env[DEFAULT_LOGGIE_TOKEN_ENV]);
66
+ return value
67
+ ? { status: "available", source: `env:${DEFAULT_LOGGIE_TOKEN_ENV}`, value }
68
+ : { status: "configured_unavailable", source: `env:${DEFAULT_LOGGIE_TOKEN_ENV}` };
69
+ }
70
+ const credentialRef = params.raw.credentialRef;
71
+ const envName = readEnvSecretRefName(credentialRef);
72
+ if (envName) {
73
+ const value = readString(params.env[envName]);
74
+ return value
75
+ ? { status: "available", source: `env:${envName}`, value }
76
+ : { status: "configured_unavailable", source: `env:${envName}` };
77
+ }
78
+ if (typeof credentialRef === "string") {
79
+ const resolvedSecretValue = readString(credentialRef);
80
+ return resolvedSecretValue
81
+ ? { status: "available", source: "credentialRef", value: resolvedSecretValue }
82
+ : { status: "missing", source: "missing" };
83
+ }
84
+ const secretRefLabel = readSecretRefLabel(credentialRef);
85
+ if (!secretRefLabel) {
86
+ return { status: "missing", source: "missing" };
87
+ }
88
+ return { status: "configured_unavailable", source: secretRefLabel };
89
+ }
90
+ function normalizeReconnect(raw) {
91
+ const reconnect = isRecord(raw.reconnect) ? raw.reconnect : {};
92
+ const minMs = Math.max(100, readInteger(reconnect.minMs, 1000));
93
+ const maxMs = Math.max(minMs, readInteger(reconnect.maxMs, 60_000));
94
+ return { minMs, maxMs };
95
+ }
96
+ function normalizeHeartbeat(raw) {
97
+ const heartbeat = isRecord(raw.heartbeat) ? raw.heartbeat : {};
98
+ return {
99
+ timeoutMs: Math.max(1000, readInteger(heartbeat.timeoutMs, DEFAULT_LOGGIE_HEARTBEAT_TIMEOUT_MS)),
100
+ };
101
+ }
102
+ function normalizeTranscript(raw) {
103
+ const transcript = isRecord(raw.transcript) ? raw.transcript : {};
104
+ return {
105
+ activation: "final-only",
106
+ debounceMs: Math.max(0, readInteger(transcript.debounceMs, 5000)),
107
+ };
108
+ }
109
+ export function listLoggieAccountIds(cfg) {
110
+ const section = getLoggieSection(cfg);
111
+ const accounts = Object.keys(getAccounts(section));
112
+ if (accounts.length > 0) {
113
+ return accounts;
114
+ }
115
+ return [readString(section.accountId) ?? DEFAULT_LOGGIE_ACCOUNT_ID];
116
+ }
117
+ export function resolveDefaultLoggieAccountId(cfg) {
118
+ const section = getLoggieSection(cfg);
119
+ return readString(section.accountId) ?? DEFAULT_LOGGIE_ACCOUNT_ID;
120
+ }
121
+ export function resolveLoggieAccount(cfg, accountId, env = process.env) {
122
+ const section = getLoggieSection(cfg);
123
+ const resolvedAccountId = accountId?.trim() || resolveDefaultLoggieAccountId(cfg);
124
+ const raw = mergeAccountConfig(section, resolvedAccountId);
125
+ const baseUrl = readString(raw.baseUrl) ?? readString(env[DEFAULT_LOGGIE_BASE_URL_ENV]) ?? "http://127.0.0.1:8787";
126
+ const agentProfileId = readString(raw.agentProfileId) ?? "";
127
+ const tokenStatus = resolveToken({ raw, env });
128
+ const enabled = readBoolean(raw.enabled, true);
129
+ const configured = Boolean(baseUrl && agentProfileId && tokenStatus.status === "available");
130
+ return {
131
+ accountId: resolvedAccountId,
132
+ name: readString(raw.name),
133
+ enabled,
134
+ configured,
135
+ baseUrl,
136
+ socketPath: readString(raw.socketPath) ?? DEFAULT_LOGGIE_SOCKET_PATH,
137
+ agentId: readString(raw.agentId) ?? DEFAULT_LOGGIE_AGENT_ID,
138
+ agentProfileId,
139
+ authHeader: normalizeAuthHeader(raw.authHeader),
140
+ tokenStatus,
141
+ reconnect: normalizeReconnect(raw),
142
+ heartbeat: normalizeHeartbeat(raw),
143
+ transcript: normalizeTranscript(raw),
144
+ };
145
+ }
146
+ export function inspectLoggieAccount(cfg, accountId) {
147
+ const account = resolveLoggieAccount(cfg, accountId);
148
+ return {
149
+ accountId: account.accountId,
150
+ name: account.name,
151
+ enabled: account.enabled,
152
+ configured: account.configured,
153
+ baseUrl: account.baseUrl,
154
+ socketPath: account.socketPath,
155
+ agentId: account.agentId,
156
+ agentProfileId: account.agentProfileId,
157
+ authHeader: account.authHeader,
158
+ tokenStatus: account.tokenStatus.status,
159
+ tokenSource: account.tokenStatus.source,
160
+ reconnect: account.reconnect,
161
+ heartbeat: account.heartbeat,
162
+ transcript: account.transcript,
163
+ };
164
+ }
165
+ export function applyLoggieAccountConfig(params) {
166
+ const next = structuredClone(params.cfg);
167
+ const channels = isRecord(next.channels) ? next.channels : {};
168
+ const loggie = isRecord(channels.loggie) ? channels.loggie : {};
169
+ channels.loggie = {
170
+ ...loggie,
171
+ accountId: params.accountId,
172
+ accounts: {
173
+ ...(isRecord(loggie.accounts) ? loggie.accounts : {}),
174
+ [params.accountId]: {
175
+ enabled: true,
176
+ ...params.input,
177
+ },
178
+ },
179
+ };
180
+ next.channels = channels;
181
+ return next;
182
+ }
@@ -0,0 +1,72 @@
1
+ import { type OpenClawConfig } from "openclaw/plugin-sdk/channel-core";
2
+ import { type ResolvedLoggieAccount } from "./account.js";
3
+ export declare const loggiePlugin: {
4
+ status: {
5
+ defaultRuntime: {
6
+ accountId: string;
7
+ running: false;
8
+ lastStartAt: null;
9
+ lastStopAt: null;
10
+ lastError: null;
11
+ } & import("./status.js").LoggieRuntimeExtra;
12
+ buildAccountSnapshot: ({ account, runtime, }: {
13
+ account: ResolvedLoggieAccount;
14
+ runtime?: Record<string, unknown> | null;
15
+ }) => Record<string, unknown> & {
16
+ mode: string;
17
+ baseUrl: string;
18
+ agentId: string;
19
+ agentProfileId: string;
20
+ tokenStatus: "available" | "configured_unavailable" | "missing";
21
+ tokenSource: string;
22
+ connected: {};
23
+ authenticated: {};
24
+ caughtUp: {};
25
+ lastEventId: {} | null;
26
+ lastCursor: {} | null;
27
+ lastSequence: {} | null;
28
+ lastDispatchAt: {} | null;
29
+ lastDeadLetterAt: {} | null;
30
+ lastDeadLetteredEventId: {} | null;
31
+ lastDeadLetteredCursor: {} | null;
32
+ lastDeadLetterReason: {} | null;
33
+ lastDeadLetterAttempts: {} | null;
34
+ };
35
+ };
36
+ doctor: {
37
+ collectPreviewWarnings: (params: {
38
+ cfg: OpenClawConfig;
39
+ env?: NodeJS.ProcessEnv;
40
+ }) => string[];
41
+ };
42
+ secrets: {
43
+ secretTargetRegistryEntries: {
44
+ id: string;
45
+ targetType: string;
46
+ configFile: string;
47
+ pathPattern: string;
48
+ secretShape: string;
49
+ expectedResolvedValue: string;
50
+ includeInPlan: boolean;
51
+ includeInConfigure: boolean;
52
+ includeInAudit: boolean;
53
+ }[];
54
+ };
55
+ gateway: {
56
+ startAccount: (ctx: {
57
+ account: ResolvedLoggieAccount;
58
+ cfg: OpenClawConfig;
59
+ runtime: unknown;
60
+ channelRuntime?: unknown;
61
+ abortSignal?: AbortSignal;
62
+ setStatus: (next: unknown) => void;
63
+ getStatus: () => unknown;
64
+ log?: {
65
+ info?: (message: string) => void;
66
+ warn?: (message: string) => void;
67
+ };
68
+ }) => Promise<void>;
69
+ };
70
+ id: string;
71
+ config?: Record<string, unknown>;
72
+ };