@50arms/session-telemetry 0.0.0-stage → 0.1.66
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 +68 -2
- package/dist/.source-hash +1 -0
- package/dist/activity-observer.d.ts +186 -0
- package/dist/activity-observer.js +917 -0
- package/dist/api-tool-names.d.ts +12 -0
- package/dist/api-tool-names.js +19 -0
- package/dist/browser.d.ts +29 -0
- package/dist/browser.js +10 -0
- package/dist/catalog-prices.d.ts +27 -0
- package/dist/catalog-prices.js +31 -0
- package/dist/claim-tracker.d.ts +53 -0
- package/dist/claim-tracker.js +213 -0
- package/dist/dispatch-usage.d.ts +321 -0
- package/dist/dispatch-usage.js +657 -0
- package/dist/git-head.d.ts +1 -0
- package/dist/git-head.js +19 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +49 -0
- package/dist/internal-logger.d.ts +16 -0
- package/dist/internal-logger.js +27 -0
- package/dist/ledger-timing.d.ts +26 -0
- package/dist/ledger-timing.js +31 -0
- package/dist/pricing.d.ts +93 -0
- package/dist/pricing.js +77 -0
- package/dist/real-work.d.ts +13 -0
- package/dist/real-work.js +57 -0
- package/dist/session-log-watcher.d.ts +616 -0
- package/dist/session-log-watcher.js +1285 -0
- package/dist/session-title-marks.d.ts +2 -0
- package/dist/session-title-marks.js +16 -0
- package/dist/stats-wire.d.ts +345 -0
- package/dist/stats-wire.js +636 -0
- package/dist/subagent-live.d.ts +36 -0
- package/dist/subagent-live.js +153 -0
- package/dist/tool-summary.d.ts +19 -0
- package/dist/tool-summary.js +58 -0
- package/dist/transcript-timing.d.ts +100 -0
- package/dist/transcript-timing.js +231 -0
- package/dist/types.d.ts +25 -0
- package/dist/types.js +1 -0
- package/dist/usage-ledger.d.ts +256 -0
- package/dist/usage-ledger.js +581 -0
- package/dist/write-queue.d.ts +9 -0
- package/dist/write-queue.js +16 -0
- package/package.json +43 -4
package/README.md
CHANGED
|
@@ -1,3 +1,69 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@50arms/session-telemetry`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
DX-2962 — the shared Claude Code transcript collector, extracted from
|
|
4
|
+
danxbot root's `src/agent/session-log-watcher.ts` so the danxbot worker and
|
|
5
|
+
the `platform-mcp` MCP server's plan event relay (DX-2954) run **identical** collector
|
|
6
|
+
code, instead of the worker's copy silently diverging from a re-implementation
|
|
7
|
+
inside the MCP package.
|
|
8
|
+
|
|
9
|
+
## What's here
|
|
10
|
+
|
|
11
|
+
- `session-log-watcher.ts` — `SessionLogWatcher`: incremental tailing of a
|
|
12
|
+
Claude Code session's main + sub-agent JSONL transcripts. No whole-transcript
|
|
13
|
+
buffer — small per-reply bookkeeping only, and only for the life of the
|
|
14
|
+
process (see its own header for the DX-2962 "unbounded entries list
|
|
15
|
+
deleted" note).
|
|
16
|
+
- `types.ts` — `AgentLogEntry` / `AgentLineage`, the collector's emitted
|
|
17
|
+
shape. Canonical home now; danxbot root's `src/types.ts` re-exports both
|
|
18
|
+
names verbatim.
|
|
19
|
+
- `tool-summary.ts` — human-readable summary builders the collector uses.
|
|
20
|
+
- `subagent-live.ts` — `createSubagentLiveView` (DX-4508): per-sub-agent snapshots
|
|
21
|
+
(tokens, cost, tool calls, model, current activity, card) computed by the same
|
|
22
|
+
activity observer, ledger counter and pricing rule the dashboard's numbers come
|
|
23
|
+
from; streamed by `platform-mcp subagents-live`.
|
|
24
|
+
- `internal-logger.ts` — this package's own tiny logging shim (see its
|
|
25
|
+
header for why it does not reuse root's `src/logger.ts`).
|
|
26
|
+
- `index.ts` — the main entry (Node-only; imports `node:fs`/`node:path`/`node:os`).
|
|
27
|
+
- `browser.ts` — the `./browser` subpath. Zero `node:` imports, enforced by
|
|
28
|
+
`src/__tests__/no-node-imports.test.ts`. Today it re-exports only the two
|
|
29
|
+
types (`AgentLogEntry`/`AgentLineage`); DX-2963 adds the first runtime
|
|
30
|
+
export (the usage counter + pricing function).
|
|
31
|
+
|
|
32
|
+
## Isolation
|
|
33
|
+
|
|
34
|
+
This package **never imports from the danxbot root repo**. Everything it
|
|
35
|
+
needs (logging, tool-summary formatting, the entry/lineage types) is either
|
|
36
|
+
moved in wholesale (when the root file had no other consumer) or
|
|
37
|
+
re-implemented minimally as this package's own dependency (when the root
|
|
38
|
+
file — like `src/logger.ts`, with ~300 unrelated consumers — was never a
|
|
39
|
+
candidate to move). See `session-log-watcher.ts` and `internal-logger.ts`'s
|
|
40
|
+
own header comments for the per-file reasoning.
|
|
41
|
+
|
|
42
|
+
## How consumers resolve this package (DX-2962 AC3)
|
|
43
|
+
|
|
44
|
+
| Consumer | Mechanism | Why |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| **danxbot worker** (root repo, `tsx src/index.ts`) | npm **workspace** link (`"workspaces": ["packages/*"]` in root `package.json`, `"@50arms/session-telemetry"` in root `dependencies`) | The worker and this package always ship from the SAME repo commit — a workspace link means the worker runs whatever source the current commit carries, with zero drift risk and no dependency on registry propagation timing. The Docker image builds `dist/` for it in a dedicated builder stage (alongside `frontend-builder` in `Dockerfile`) since the final image's `npm ci --omit=dev` never installs this package's own `devDependencies` (`typescript`). |
|
|
47
|
+
| **`platform-mcp` MCP server** (separate npm-published package, DX-2954) | Ordinary npm dependency, **pinned to the exact published version** the worker's commit ships | The MCP server is a genuinely separate installable artifact (`npx -y @50arms/platform-mcp`), so it cannot workspace-link into danxbot's own checkout — it needs the real published bytes. Pinning exact (not `^`/`~`) is what makes "worker and MCP run identical collector code for any given release" true: MCP's own version bump is the only thing that ever changes what collector code it runs. |
|
|
48
|
+
| **Dashboard server** (`src/dashboard/*`) | Same npm dependency shape as the worker (the dashboard server's `src/` is root-repo code, so it resolves through the SAME root workspace link as the worker) | An npm dependency resolves through ordinary node_modules lookup, so no `@backend`-style alias is needed. |
|
|
49
|
+
|
|
50
|
+
## Release order (DX-2962 AC3)
|
|
51
|
+
|
|
52
|
+
1. Publish this package (`make publish-session-telemetry`).
|
|
53
|
+
2. Publish `platform-mcp` (only once it depends on this package — DX-2954).
|
|
54
|
+
3. Deploy the danxbot backend.
|
|
55
|
+
|
|
56
|
+
Publishing comes before deploying because the publish-drift gate
|
|
57
|
+
(`src/dispatch/publish-drift-gate.ts`) refuses a finalize whose bumped
|
|
58
|
+
`packages/*/package.json` version is not yet live on npm.
|
|
59
|
+
|
|
60
|
+
## Boot check
|
|
61
|
+
|
|
62
|
+
`npm run verify:boot` (wired into `make publish-session-telemetry`,
|
|
63
|
+
mirroring `packages/danx-dashboard-mcp/scripts/verify-boot.sh`) packs the
|
|
64
|
+
freshly-built package with `npm pack`, installs the tarball into a clean
|
|
65
|
+
temp directory (the way an `npm install` consumer actually would — not a
|
|
66
|
+
relative `require` back into this checkout), and imports both the main
|
|
67
|
+
entry and the `./browser` subpath, calling one exported function from each.
|
|
68
|
+
A broken `exports` map, a missing `dist/` file, or an accidental `node:`
|
|
69
|
+
leak into `browser.ts` fails the publish before anything reaches npm.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2ba7b19c6a4f26c9d1b0a2798a82b2e55e3f5aac580245587742c3332dcf6c25
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DX-2954 — the TRANSCRIPT-side producer of a dispatch's running set.
|
|
3
|
+
*
|
|
4
|
+
* `dispatch_activities` answers "what is this dispatch running RIGHT NOW" — one
|
|
5
|
+
* row per sub-agent or background script, with `finished_at` NULL while it is
|
|
6
|
+
* still going. This module produces the sub-agent half of that set from the one
|
|
7
|
+
* signal danxbot owns end-to-end: the session-log watcher discovering a
|
|
8
|
+
* sub-agent's transcript file and then tailing it.
|
|
9
|
+
*
|
|
10
|
+
* ## Why the transcript, and where the hooks come in
|
|
11
|
+
*
|
|
12
|
+
* Claude Code's `SubagentStart` / `SubagentStop` hooks carry an `agent_type`
|
|
13
|
+
* before the sub-agent has written anything and a FINISH for every sub-agent,
|
|
14
|
+
* foreground or background. They ship from the 50arms plugin in the
|
|
15
|
+
* claude-plugins repo (DX-3284; DX-4235 moved them into the plugin's hooks module), which calls
|
|
16
|
+
* `50arms_api`, and reach the server
|
|
17
|
+
* through `POST /api/plan-sessions/me/activity` — which writes the SAME
|
|
18
|
+
* `dispatch_activities` rows through the same one server writer.
|
|
19
|
+
*
|
|
20
|
+
* The transcript stays the producer because it needs no hook to be installed: a
|
|
21
|
+
* session with no plugin, and every WORKER dispatch, still gets a row for each
|
|
22
|
+
* sub-agent from here. The hooks are an ENRICHMENT layered on the same rows:
|
|
23
|
+
*
|
|
24
|
+
* | Signal | Sub-agent start | Sub-agent finish | Background script |
|
|
25
|
+
* |---|---|---|---|
|
|
26
|
+
* | Transcript (here) | file discovered | a foreground sub-agent's own `tool_result` in the spawner's transcript (DX-5576); a background sub-agent's task notification (DX-4498); else the dispatch's terminal write (`finishOpenActivities`) as the fallback | not visible |
|
|
27
|
+
* | Hooks (DX-3284, operator sessions) | `SubagentStart` | `SubagentStop` | `PostToolUse` start; end at dispatch terminal (no documented completion event) |
|
|
28
|
+
*
|
|
29
|
+
* ## What it reports about each sub-agent (DX-4498)
|
|
30
|
+
*
|
|
31
|
+
* All of it read from what Claude Code itself writes, with no cooperation from
|
|
32
|
+
* the sub-agent:
|
|
33
|
+
*
|
|
34
|
+
* | Field | Read from |
|
|
35
|
+
* |---|---|
|
|
36
|
+
* | `agentType`, `description`, `parentActivityId` | the sub-agent's `meta.json` sidecar (via `SourceDiscoveredInfo`); the description through {@link redactLabel} |
|
|
37
|
+
* | `lastActivityAt` | the timestamp of its latest `assistant` / `user` line |
|
|
38
|
+
* | `effort` | its latest `assistant` line's top-level `effort` |
|
|
39
|
+
* | `currentActivity` | its latest `tool_use` block, through {@link describeToolCall} |
|
|
40
|
+
* | `briefProfile` | DX-5898: the leading word of its brief (the root `user` line) when shaped like a profile name, else null; the server matches it against the team's profiles |
|
|
41
|
+
* | `briefIssueId` | the card its spawning `Agent` call names: the first card id in its `description` (redacted), else the first anywhere in its brief (the root `user` line) — see {@link firstIssueId} (DX-4508) |
|
|
42
|
+
* | `finishedAt`, `endStatus` | what Claude Code writes into the SPAWNER's transcript: a foreground spawn's `tool_result` (DX-5576) or a background spawn's task notification (DX-4498) |
|
|
43
|
+
*
|
|
44
|
+
* Model, tokens, cost, tool-call count and the claimed card are NOT here: the
|
|
45
|
+
* usage ledger already records them per reply, keyed by the same source, and
|
|
46
|
+
* the read derives them from it (two places recording one number is how they
|
|
47
|
+
* drift).
|
|
48
|
+
*
|
|
49
|
+
* ## Why the two producers cannot double-count
|
|
50
|
+
*
|
|
51
|
+
* Every row is an upsert keyed `(dispatch_id, kind, activity_id)`, and BOTH
|
|
52
|
+
* producers derive `activity_id` from the same thing: the sub-agent's transcript
|
|
53
|
+
* file basename, `agent-<agent_id>`. The watcher has it directly; a hook has the
|
|
54
|
+
* `agent_id` and builds the same basename from it: `agent-` plus the id with one leading `agent-` stripped (the hooks docs
|
|
55
|
+
* show the prefix on some `agent_id`s), so both forms reach this key. That rule lives in the 50arms plugin's hooks module
|
|
56
|
+
* (claude-plugins repo). A hook's `transcript_path` is NOT the
|
|
57
|
+
* sub-agent's file — on these events it is the MAIN session's transcript, and only
|
|
58
|
+
* `SubagentStop` also carries the sub-agent's own as `agent_transcript_path`,
|
|
59
|
+
* which the plugin uses to refuse a key that disagrees. That basename is also
|
|
60
|
+
* the key the ledger credits the sub-agent's tokens under, which is how
|
|
61
|
+
* per-sub-agent tokens join to the running set without this table carrying a
|
|
62
|
+
* token column. A task notification names the sub-agent by its bare id
|
|
63
|
+
* (`<task-id>`), which is mapped onto the same `agent-<id>` key.
|
|
64
|
+
*
|
|
65
|
+
* ## A finish is reported only when Claude Code says so
|
|
66
|
+
*
|
|
67
|
+
* A sub-agent's OWN transcript gives no "this sub-agent is done" signal — a
|
|
68
|
+
* finished sub-agent's file simply stops growing, which is indistinguishable
|
|
69
|
+
* from a slow one. Guessing a finish from an idle gap would mark a
|
|
70
|
+
* long-thinking sub-agent as complete, so this module never does. The finishes it
|
|
71
|
+
* reports are EXPLICIT, and a row ends one of three ways:
|
|
72
|
+
*
|
|
73
|
+
* 1. A FOREGROUND spawn's `tool_result` in the spawner's transcript (DX-5576), written when the sub-agent is done: its status is
|
|
74
|
+
* `completed`, `failed` for an errored result, `stopped` for an interrupted or rejected one ({@link foregroundEndStatus}).
|
|
75
|
+
* 2. A BACKGROUND spawn's `<task-notification>` (DX-4498), which Claude Code delivers to the spawner when the task ends, carrying
|
|
76
|
+
* its `<status>` (`completed`, `failed`, `killed`, `stopped`). A foreground spawn gets no notification.
|
|
77
|
+
* 3. The fallback, for a row neither signal reached: the `SubagentStop` hook (operator sessions) or the dispatch's own terminal
|
|
78
|
+
* write, which sets `finishedAt` but names no status.
|
|
79
|
+
*
|
|
80
|
+
* Whichever lands first sets `finishedAt`; a later signal only fills the status, unless it is a later end (DX-6815: a sub-agent resumed after its finish ends again), which replaces the finish.
|
|
81
|
+
*/
|
|
82
|
+
import { type ActivityChange, type ActivityEndStatus, type ActivityMessage, type ActivityProblemLink } from "./stats-wire.js";
|
|
83
|
+
import type { EntryMeta, SourceDiscoveredInfo } from "./session-log-watcher.js";
|
|
84
|
+
/** What the observer needs from the dispatch's usage accumulator. */
|
|
85
|
+
export interface ActivityObserverDeps {
|
|
86
|
+
/**
|
|
87
|
+
* Queue activity changes onto the next stats write. Called with the full
|
|
88
|
+
* current state of each changed activity — the server's upsert is idempotent,
|
|
89
|
+
* so re-reporting an unchanged field is free and a dropped batch is
|
|
90
|
+
* self-healing on the next write.
|
|
91
|
+
*/
|
|
92
|
+
recordActivities: (changes: ActivityChange[]) => void;
|
|
93
|
+
/**
|
|
94
|
+
* DX-2954 — ignore every sub-agent file that already existed when the watcher
|
|
95
|
+
* attached. Set for a dispatch that continues an earlier one's session
|
|
96
|
+
* (`StartCursorPlan.inheritsPredecessorSubagents`): those files are the
|
|
97
|
+
* predecessor's sub-agents, which ended with its process, and reporting them
|
|
98
|
+
* would open a never-closing "running" row per predecessor sub-agent on the
|
|
99
|
+
* new dispatch — inflating `subagent_count` / `running_activity_count` and
|
|
100
|
+
* tripping the silent-too-long warning ten minutes later.
|
|
101
|
+
*/
|
|
102
|
+
ignorePreexisting?: boolean;
|
|
103
|
+
/**
|
|
104
|
+
* DX-5333 — queue the messages read from sub-agent transcripts (see {@link ActivityMessage}). Absent for a consumer that
|
|
105
|
+
* stores nothing (the local live view).
|
|
106
|
+
*/
|
|
107
|
+
recordMessages?: (messages: ActivityMessage[]) => void;
|
|
108
|
+
/** DX-5333 — queue the card problems a sub-agent created (see {@link ActivityProblemLink}). Absent like {@link ActivityObserverDeps.recordMessages}. */
|
|
109
|
+
recordProblemLinks?: (links: ActivityProblemLink[]) => void;
|
|
110
|
+
/**
|
|
111
|
+
* DX-5333 — `git rev-parse HEAD` in `cwd`: the full object id, or null when `cwd` is not a git checkout (or git cannot be
|
|
112
|
+
* run). Absent when the consumer stamps no start commit. Called at most once per distinct `cwd` per sub-agent, and only for
|
|
113
|
+
* a sub-agent the collector saw START (never one already running when it attached, whose checkout has moved on since).
|
|
114
|
+
*/
|
|
115
|
+
readHeadSha?: (cwd: string) => Promise<string | null>;
|
|
116
|
+
}
|
|
117
|
+
export interface ActivityObserver {
|
|
118
|
+
/** Wire to the watcher's `onSourceDiscovered`. */
|
|
119
|
+
onSourceDiscovered: (info: SourceDiscoveredInfo) => void;
|
|
120
|
+
/** DX-4498 — wire to the watcher's `onUsageLine` (the raw line feed: the brief and task notifications are not entries). */
|
|
121
|
+
onLine: (raw: Record<string, unknown>, meta: EntryMeta) => void;
|
|
122
|
+
/**
|
|
123
|
+
* DX-5333 - wire to the watcher's `onPollComplete`. A sub-agent whose end was reported in an EARLIER poll (its spawn's result, or the
|
|
124
|
+
* task-notification) has all its lines read since (DX-5413: a signal read this poll may precede its child's last lines), so its held reply is confirmed here as its final.
|
|
125
|
+
*/
|
|
126
|
+
onPollComplete: () => void;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* DX-4498 — a task notification's `(task id, end status)`, or null for a line that is not one, or one whose status is
|
|
130
|
+
* not an end (`running`). Only the two delivery shapes Claude Code itself writes are read: a `user` line whose
|
|
131
|
+
* `origin.kind` is `task-notification`, and a `queued_command` attachment with that `commandMode`. A sub-agent's
|
|
132
|
+
* reply that merely QUOTES a notification carries neither marker, so it can never end anything.
|
|
133
|
+
*/
|
|
134
|
+
export declare function parseTaskNotification(raw: Record<string, unknown>): {
|
|
135
|
+
taskId: string;
|
|
136
|
+
endStatus: ActivityEndStatus;
|
|
137
|
+
} | null;
|
|
138
|
+
/** Punctuation a token may be wrapped in, which never stops it from being read as opaque. */
|
|
139
|
+
export declare const WRAPPING_PUNCTUATION: RegExp;
|
|
140
|
+
/**
|
|
141
|
+
* DX-4498 — an agent-written label as the stats wire may carry it: on one line, with every path-shaped token (any token
|
|
142
|
+
* holding a `/` or `\`) read as `[path]`, and every secret-shaped token (an `ENV_NAME=value` value, or an opaque run of
|
|
143
|
+
* 20+ letters and digits such as a key, token or full SHA) read as `[redacted]`. A label is free text the agent wrote
|
|
144
|
+
* for a person to read (real shell descriptions name absolute paths), and it reaches every reader of the team, so this
|
|
145
|
+
* is the one way any label leaves a transcript. Empty for a blank label.
|
|
146
|
+
*/
|
|
147
|
+
export declare function redactLabel(label: string): string;
|
|
148
|
+
/**
|
|
149
|
+
* DX-5333 — a message as the stats wire may carry it: every credential read as `[redacted]` — a URL's password, an
|
|
150
|
+
* `Authorization`/bearer value, the value of a credential-named assignment, JSON key or `--flag` (`password: x`,
|
|
151
|
+
* `"password": "x"`, `--password x`), and every opaque run of 20+ characters (a key, a token, a base64 secret that may hold
|
|
152
|
+
* `/` or end in `=`; a git commit id is kept) — with the text's own whitespace, line breaks, paths and prose kept: a brief is
|
|
153
|
+
* read to replay the work, and its paths are the work. NUL characters are removed (Postgres refuses them, which would wedge
|
|
154
|
+
* the write that carries the message). Cut to {@link ACTIVITY_MESSAGE_MAX_CHARS}. The server applies the same function again
|
|
155
|
+
* on receipt.
|
|
156
|
+
*
|
|
157
|
+
* The order of the chain matters: NUL goes first, the PEM block next (its body lines would otherwise be read token by token), then
|
|
158
|
+
* the structured patterns (URL credentials, headers, named values, flags), and the per-token classifier last, over what is
|
|
159
|
+
* left. The server re-runs the whole chain on text already masked here, so every step must leave `[redacted]` alone (idempotent).
|
|
160
|
+
*/
|
|
161
|
+
export declare function redactSecrets(text: string): string;
|
|
162
|
+
/** DX-4498 — a transcript value read as a label: {@link redactLabel}'s text, or null for a non-string or blank one. */
|
|
163
|
+
export declare function labelOf(value: unknown): string | null;
|
|
164
|
+
/**
|
|
165
|
+
* DX-4498 — a sub-agent's current activity, from one `tool_use` block: the tool's name, plus a label only where the
|
|
166
|
+
* agent wrote one AS a label (a shell call's `description`, an `Agent` call's `description`, a `Skill` call's skill
|
|
167
|
+
* name), through {@link labelOf}. Never a command, a file path, a secret or a prompt: the stats wire carries ids,
|
|
168
|
+
* numbers and redacted labels, never free-form transcript content.
|
|
169
|
+
*/
|
|
170
|
+
export declare function describeToolCall(name: string, input: Record<string, unknown>): string;
|
|
171
|
+
/**
|
|
172
|
+
* DX-4508 — the first card-id-shaped token in a text (`DX-4498`, also `DX-4498:`, `(DX-4498)`), or null when it has
|
|
173
|
+
* none. A sub-agent's card is the one its spawning `Agent` call names: its `description` ("Review DX-4498 code gates")
|
|
174
|
+
* first, else its brief ("Quality gate for card DX-4421 ..."), where the first-word rule DX-4498 shipped missed most
|
|
175
|
+
* real briefs. Only the SHAPE is checked: the server keeps the id only when it is a real card of the team, so a plan ref
|
|
176
|
+
* (`PLAN-2`) or rule ref (`R-45`) never reaches a reader.
|
|
177
|
+
*/
|
|
178
|
+
export declare function firstIssueId(text: string): string | null;
|
|
179
|
+
/**
|
|
180
|
+
* DX-5898 - the leading word of a sub-agent's brief when it has a profile name's shape (`gate-designer DX-5875` -> `gate-designer`), else
|
|
181
|
+
* null. An orchestrator briefs a profile sub-agent as `<profile> <CARD-ID>` (DX-5899); the collector cannot know the team's profiles, so it
|
|
182
|
+
* keeps only the raw word and the server matches it against the profiles the team may see when it reads the row. A brief that opens with an
|
|
183
|
+
* ordinary word equal to a profile's name therefore gives a false match: the shape check and that server match are the only filters.
|
|
184
|
+
*/
|
|
185
|
+
export declare function leadingProfileWord(text: string): string | null;
|
|
186
|
+
export declare function createActivityObserver(deps: ActivityObserverDeps): ActivityObserver;
|