@raidiant/notifai 9.3.0 → 10.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 (126) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +1 -1
  3. package/dist/client.d.ts +3 -1
  4. package/dist/client.js +2 -0
  5. package/dist/client.js.map +1 -1
  6. package/dist/commands-agent-sessions.d.ts +6 -0
  7. package/dist/commands-agent-sessions.js +51 -0
  8. package/dist/commands-agent-sessions.js.map +1 -0
  9. package/dist/commands-auth.js +4 -4
  10. package/dist/commands-auth.js.map +1 -1
  11. package/dist/commands-config.js +11 -4
  12. package/dist/commands-config.js.map +1 -1
  13. package/dist/commands-core.d.ts +5 -0
  14. package/dist/commands-core.js +2 -0
  15. package/dist/commands-core.js.map +1 -1
  16. package/dist/commands-doctor.js +121 -40
  17. package/dist/commands-doctor.js.map +1 -1
  18. package/dist/commands-harness-context.d.ts +15 -2
  19. package/dist/commands-harness-context.js +40 -1
  20. package/dist/commands-harness-context.js.map +1 -1
  21. package/dist/commands-hooks.d.ts +4 -3
  22. package/dist/commands-hooks.js +139 -16
  23. package/dist/commands-hooks.js.map +1 -1
  24. package/dist/commands-init.js +10 -5
  25. package/dist/commands-init.js.map +1 -1
  26. package/dist/commands-send-support.js +18 -1
  27. package/dist/commands-send-support.js.map +1 -1
  28. package/dist/commands-send.js +2 -2
  29. package/dist/commands-send.js.map +1 -1
  30. package/dist/commands-sounds.d.ts +12 -0
  31. package/dist/commands-sounds.js +35 -0
  32. package/dist/commands-sounds.js.map +1 -0
  33. package/dist/commands.d.ts +2 -0
  34. package/dist/commands.js +2 -0
  35. package/dist/commands.js.map +1 -1
  36. package/dist/config-schema.js +5 -2
  37. package/dist/config-schema.js.map +1 -1
  38. package/dist/config.d.ts +3 -4
  39. package/dist/config.js +5 -2
  40. package/dist/config.js.map +1 -1
  41. package/dist/credentials.js +4 -1
  42. package/dist/credentials.js.map +1 -1
  43. package/dist/harnesses.d.ts +49 -7
  44. package/dist/harnesses.js +110 -24
  45. package/dist/harnesses.js.map +1 -1
  46. package/dist/hooks.d.ts +2 -2
  47. package/dist/hooks.js +2 -2
  48. package/dist/hooks.js.map +1 -1
  49. package/dist/install-hooks.d.ts +10 -11
  50. package/dist/install-hooks.js +18 -13
  51. package/dist/install-hooks.js.map +1 -1
  52. package/dist/interactive.js +20 -23
  53. package/dist/interactive.js.map +1 -1
  54. package/dist/invocation-context.d.ts +2 -2
  55. package/dist/openclaw-plugin.d.ts +49 -0
  56. package/dist/openclaw-plugin.js +350 -0
  57. package/dist/openclaw-plugin.js.map +1 -0
  58. package/dist/orca-session-title.d.ts +10 -0
  59. package/dist/orca-session-title.js +98 -0
  60. package/dist/orca-session-title.js.map +1 -0
  61. package/dist/program.d.ts +3 -1
  62. package/dist/program.js +37 -13
  63. package/dist/program.js.map +1 -1
  64. package/dist/send.js +11 -14
  65. package/dist/send.js.map +1 -1
  66. package/dist/session-activation.js +1 -1
  67. package/dist/session-activation.js.map +1 -1
  68. package/dist/session-labels.d.ts +18 -2
  69. package/dist/session-labels.js +79 -8
  70. package/dist/session-labels.js.map +1 -1
  71. package/dist/sound-ref.d.ts +23 -0
  72. package/dist/sound-ref.js +28 -0
  73. package/dist/sound-ref.js.map +1 -0
  74. package/dist/ui/help.d.ts +4 -5
  75. package/dist/ui/help.js +5 -6
  76. package/dist/ui/help.js.map +1 -1
  77. package/package.json +3 -3
  78. package/src/client.test.ts +12 -0
  79. package/src/client.ts +8 -0
  80. package/src/commands-agent-sessions.ts +72 -0
  81. package/src/commands-auth.ts +4 -3
  82. package/src/commands-config.ts +10 -4
  83. package/src/commands-core.ts +6 -0
  84. package/src/commands-doctor.ts +143 -38
  85. package/src/commands-harness-context.ts +56 -2
  86. package/src/commands-hooks.ts +180 -20
  87. package/src/commands-init.ts +10 -4
  88. package/src/commands-send-support.ts +23 -1
  89. package/src/commands-send.ts +2 -2
  90. package/src/commands-sounds.test.ts +106 -0
  91. package/src/commands-sounds.ts +49 -0
  92. package/src/commands.test.ts +529 -17
  93. package/src/commands.ts +5 -0
  94. package/src/config-schema.test.ts +1 -0
  95. package/src/config-schema.ts +5 -2
  96. package/src/config.test.ts +42 -20
  97. package/src/config.ts +6 -5
  98. package/src/credentials.test.ts +20 -0
  99. package/src/credentials.ts +4 -1
  100. package/src/fixtures/hermes-0.20.6-classic-cli-local.json +50 -0
  101. package/src/fixtures/openclaw-2026.7.1-2-agent-local.json +57 -0
  102. package/src/harness-integration.test.ts +294 -0
  103. package/src/harnesses.test.ts +115 -8
  104. package/src/harnesses.ts +133 -30
  105. package/src/hooks.test.ts +4 -3
  106. package/src/hooks.ts +4 -4
  107. package/src/install-hooks.test.ts +213 -4
  108. package/src/install-hooks.ts +38 -30
  109. package/src/interactive.test.ts +31 -0
  110. package/src/interactive.ts +20 -26
  111. package/src/invocation-context.ts +2 -2
  112. package/src/openclaw-plugin.ts +409 -0
  113. package/src/orca-session-title.test.ts +54 -0
  114. package/src/orca-session-title.ts +103 -0
  115. package/src/program.test.ts +27 -0
  116. package/src/program.ts +47 -12
  117. package/src/send.ts +10 -19
  118. package/src/session-activation.ts +1 -1
  119. package/src/session-labels.test.ts +58 -9
  120. package/src/session-labels.ts +95 -12
  121. package/src/skill.test.ts +14 -3
  122. package/src/sound-ref.test.ts +31 -0
  123. package/src/sound-ref.ts +34 -0
  124. package/src/ui/config-view.test.ts +1 -0
  125. package/src/ui/help.test.ts +16 -0
  126. package/src/ui/help.ts +6 -7
@@ -0,0 +1,350 @@
1
+ import path from 'node:path';
2
+ import { hookHostPlatform } from './hook-adapter.js';
3
+ import { harnessAccountHome } from './install-hooks.js';
4
+ import { MISSING_LIFECYCLE_GUIDANCE_CONTEXT, WORKER_ACTIVATION_CONTEXT, } from './session-activation.js';
5
+ /**
6
+ * The OpenClaw adapter.
7
+ *
8
+ * OpenClaw's extension point is an in-process Gateway plugin, not a command
9
+ * hook. The generated module shells out to the same `notifai hook <event>`
10
+ * commands every other harness invokes, so presence, activation, and
11
+ * retirement stay in the CLI.
12
+ *
13
+ * before_prompt_build -> session-start / subagent-start (first owner/worker)
14
+ * message_received -> user-prompt-submit (User is present)
15
+ * agent_end -> stop (the turn ended)
16
+ * session_end -> session-end (retire local state)
17
+ * resolve_exec_env -> NOTIFAI_ACTIVE_* markers (exact Source Context)
18
+ *
19
+ * `command:stop` is a User abort and is deliberately not wired. Asynchronous
20
+ * ask is unsupported until a live continuation probe.
21
+ */
22
+ export const OPENCLAW_PLUGIN_MARKER = '// notifai managed openclaw plugin';
23
+ export const OPENCLAW_PLUGIN_ID = 'notifai';
24
+ export const OPENCLAW_PLUGIN_FILENAME = 'index.js';
25
+ export const OPENCLAW_PLUGIN_MANIFEST = 'openclaw.plugin.json';
26
+ export const OPENCLAW_PLUGIN_PACKAGE = 'package.json';
27
+ const OPENCLAW_ADAPTER_VERSION = 1;
28
+ export const OPENCLAW_EVENTS = [
29
+ ['SessionStart', 'session-start'],
30
+ ['SubagentStart', 'subagent-start'],
31
+ ['UserPromptSubmit', 'user-prompt-submit'],
32
+ ['Stop', 'stop'],
33
+ ['SessionEnd', 'session-end'],
34
+ ];
35
+ export function openclawStateDir(env = process.env, platform = process.platform) {
36
+ const override = env['OPENCLAW_STATE_DIR'];
37
+ if (override !== undefined && override !== '')
38
+ return override;
39
+ const homeOverride = env['OPENCLAW_HOME'];
40
+ if (homeOverride !== undefined && homeOverride !== '') {
41
+ return path.join(homeOverride, '.openclaw');
42
+ }
43
+ return path.join(harnessAccountHome(env, platform), '.openclaw');
44
+ }
45
+ export function openclawConfigPath(env = process.env, platform = process.platform) {
46
+ const override = env['OPENCLAW_CONFIG_PATH'];
47
+ if (override !== undefined && override !== '')
48
+ return override;
49
+ return path.join(openclawStateDir(env, platform), 'openclaw.json');
50
+ }
51
+ export function openclawPluginDir(global, cwd, env = process.env, platform = process.platform) {
52
+ return global
53
+ ? path.join(openclawStateDir(env, platform), 'extensions', OPENCLAW_PLUGIN_ID)
54
+ : path.join(cwd, '.openclaw', 'extensions', OPENCLAW_PLUGIN_ID);
55
+ }
56
+ export function openclawPluginPath(global, cwd, env = process.env, platform = process.platform) {
57
+ return path.join(openclawPluginDir(global, cwd, env, platform), OPENCLAW_PLUGIN_FILENAME);
58
+ }
59
+ export function openclawPluginManifest() {
60
+ return `${JSON.stringify({
61
+ id: OPENCLAW_PLUGIN_ID,
62
+ name: 'Notifai',
63
+ description: 'Notifai lifecycle activation, Source Context, and session observation.',
64
+ activation: { onStartup: true },
65
+ configSchema: { type: 'object', additionalProperties: false },
66
+ }, null, 2)}\n`;
67
+ }
68
+ export function openclawPluginPackage() {
69
+ return `${JSON.stringify({
70
+ name: 'notifai-openclaw',
71
+ version: '1.0.0',
72
+ type: 'module',
73
+ openclaw: { extensions: [`./${OPENCLAW_PLUGIN_FILENAME}`] },
74
+ }, null, 2)}\n`;
75
+ }
76
+ export function openclawPluginSource(options) {
77
+ const { adapterPath, timeoutSeconds } = options;
78
+ const win32 = hookHostPlatform(options.platform) === 'win32';
79
+ const nodeConstant = win32
80
+ ? `const NODE = ${JSON.stringify(options.nodePath ?? process.execPath)}\n`
81
+ : '';
82
+ const spawnArguments = win32
83
+ ? 'NODE, [ADAPTER, "hook", event, "--owner", "notifai", "--harness", "openclaw"]'
84
+ : 'ADAPTER, ["hook", event, "--owner", "notifai", "--harness", "openclaw"]';
85
+ const windowsHide = win32 ? '\n windowsHide: true,' : '';
86
+ return `${OPENCLAW_PLUGIN_MARKER}
87
+ // Generated by \`notifai hooks install --harness openclaw\`. Edits are lost on
88
+ // the next install; change the CLI instead, which is where the logic lives.
89
+ import { spawn } from "node:child_process"
90
+
91
+ ${nodeConstant}const ADAPTER = ${JSON.stringify(adapterPath)}
92
+ const TIMEOUT_MS = ${timeoutSeconds * 1000}
93
+ const ADAPTER_VERSION = ${OPENCLAW_ADAPTER_VERSION}
94
+ const MISSING_LIFECYCLE_GUIDANCE_CONTEXT = ${JSON.stringify(MISSING_LIFECYCLE_GUIDANCE_CONTEXT)}
95
+ const WORKER_ACTIVATION_CONTEXT = ${JSON.stringify(WORKER_ACTIVATION_CONTEXT)}
96
+
97
+ function runHook(event, envelope) {
98
+ return new Promise((resolve) => {
99
+ let child
100
+ try {
101
+ child = spawn(${spawnArguments}, {
102
+ env: process.env,
103
+ shell: false,${windowsHide}
104
+ stdio: ["pipe", "pipe", "pipe"],
105
+ })
106
+ } catch {
107
+ resolve(null)
108
+ return
109
+ }
110
+
111
+ let stdout = ""
112
+ let settled = false
113
+ const finish = (value) => {
114
+ if (settled) return
115
+ settled = true
116
+ clearTimeout(timer)
117
+ resolve(value)
118
+ }
119
+ const timer = setTimeout(() => {
120
+ child.kill("SIGTERM")
121
+ finish(null)
122
+ }, TIMEOUT_MS)
123
+
124
+ child.stdout?.setEncoding("utf-8")
125
+ child.stdout?.on("data", (chunk) => {
126
+ stdout += chunk
127
+ })
128
+ child.stderr?.resume()
129
+ child.on("error", () => finish(null))
130
+ child.on("close", () => finish(stdout))
131
+
132
+ try {
133
+ child.stdin?.end(JSON.stringify(envelope))
134
+ } catch {
135
+ finish(null)
136
+ }
137
+ })
138
+ }
139
+
140
+ function sessionKeyOf(event, ctx) {
141
+ const key = ctx?.sessionKey ?? event?.sessionKey ?? event?.context?.sessionKey
142
+ return typeof key === "string" ? key.trim() : ""
143
+ }
144
+
145
+ function workspaceDirOf(event, ctx) {
146
+ const dir = ctx?.workspaceDir ?? event?.workspaceDir ?? event?.context?.workspaceDir
147
+ return typeof dir === "string" && dir.length > 0 ? dir : process.cwd()
148
+ }
149
+
150
+ function isWorkerSession(sessionKey, ctx) {
151
+ if (sessionKey === "") return true
152
+ if (sessionKey.includes(":subagent:")) return true
153
+ if (sessionKey.includes(":acp:")) return true
154
+ if (ctx?.targetKind === "acp" || ctx?.dispatchKind === "acp") return true
155
+ if (typeof ctx?.parentSessionKey === "string" && ctx.parentSessionKey.length > 0) return true
156
+ return false
157
+ }
158
+
159
+ function isUserMessage(event, ctx) {
160
+ const from = event?.from ?? event?.context?.from ?? ctx?.senderId
161
+ if (typeof from !== "string" || from.trim().length === 0) return false
162
+ const trigger = ctx?.trigger ?? event?.trigger
163
+ if (trigger === "cron" || trigger === "heartbeat") return false
164
+ return true
165
+ }
166
+
167
+ function register(api) {
168
+ const activated = new Set()
169
+ api.on("before_prompt_build", async (event, ctx) => {
170
+ const sessionKey = sessionKeyOf(event, ctx)
171
+ if (sessionKey === "") return { prependContext: WORKER_ACTIVATION_CONTEXT }
172
+ if (activated.has(sessionKey)) return
173
+ const worker = isWorkerSession(sessionKey, ctx)
174
+ const resolved = await runHook(worker ? "subagent-start" : "session-start", {
175
+ session_id: sessionKey,
176
+ cwd: workspaceDirOf(event, ctx),
177
+ hook_event_name: worker ? "SubagentStart" : "SessionStart",
178
+ })
179
+ const context = typeof resolved === "string" && resolved.trim().length > 0
180
+ ? resolved.trim()
181
+ : worker ? WORKER_ACTIVATION_CONTEXT : MISSING_LIFECYCLE_GUIDANCE_CONTEXT
182
+ activated.add(sessionKey)
183
+ return { prependContext: context }
184
+ })
185
+
186
+ api.on("message_received", async (event, ctx) => {
187
+ if (!isUserMessage(event, ctx)) return
188
+ const sessionKey = sessionKeyOf(event, ctx)
189
+ if (sessionKey === "") return
190
+ await runHook("user-prompt-submit", {
191
+ session_id: sessionKey,
192
+ cwd: workspaceDirOf(event, ctx),
193
+ hook_event_name: "UserPromptSubmit",
194
+ })
195
+ })
196
+
197
+ api.on("agent_end", async (event, ctx) => {
198
+ const sessionKey = sessionKeyOf(event, ctx)
199
+ if (sessionKey === "") return
200
+ await runHook("stop", {
201
+ session_id: sessionKey,
202
+ cwd: workspaceDirOf(event, ctx),
203
+ hook_event_name: "Stop",
204
+ })
205
+ })
206
+
207
+ api.on("session_end", async (event, ctx) => {
208
+ const sessionKey = sessionKeyOf(event, ctx)
209
+ if (sessionKey === "") return
210
+ activated.delete(sessionKey)
211
+ await runHook("session-end", {
212
+ session_id: sessionKey,
213
+ cwd: workspaceDirOf(event, ctx),
214
+ hook_event_name: "SessionEnd",
215
+ })
216
+ })
217
+
218
+ api.on("resolve_exec_env", (event, ctx) => {
219
+ const sessionKey = sessionKeyOf(event, ctx)
220
+ if (sessionKey === "") return
221
+ return {
222
+ NOTIFAI_ACTIVE_HARNESS: "openclaw",
223
+ NOTIFAI_ACTIVE_SESSION_ID: sessionKey,
224
+ }
225
+ })
226
+ }
227
+
228
+ export default {
229
+ id: ${JSON.stringify(OPENCLAW_PLUGIN_ID)},
230
+ name: "Notifai",
231
+ register,
232
+ }
233
+ `;
234
+ }
235
+ export function isOurOpenclawPlugin(contents) {
236
+ return contents.includes(OPENCLAW_PLUGIN_MARKER);
237
+ }
238
+ export function openclawPluginTarget(contents) {
239
+ if (!isOurOpenclawPlugin(contents))
240
+ return null;
241
+ const adapter = /^const ADAPTER = (".*")$/m.exec(contents)?.[1];
242
+ const node = /^const NODE = (".*")$/m.exec(contents)?.[1];
243
+ const version = Number(/^const ADAPTER_VERSION = (\d+)$/m.exec(contents)?.[1] ?? 0);
244
+ const timeoutMs = Number(/^const TIMEOUT_MS = (\d+)$/m.exec(contents)?.[1] ?? Number.NaN);
245
+ if (adapter === undefined)
246
+ return null;
247
+ try {
248
+ return {
249
+ adapter: JSON.parse(adapter),
250
+ current: version === OPENCLAW_ADAPTER_VERSION,
251
+ ...(Number.isFinite(timeoutMs) ? { timeoutSeconds: timeoutMs / 1000 } : {}),
252
+ ...(node === undefined ? {} : { nodePath: JSON.parse(node) }),
253
+ };
254
+ }
255
+ catch {
256
+ return null;
257
+ }
258
+ }
259
+ export function parseOpenclawConfig(source) {
260
+ try {
261
+ const parsed = JSON.parse(source);
262
+ if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) {
263
+ return parsed;
264
+ }
265
+ }
266
+ catch {
267
+ const stripped = source
268
+ .replace(/\/\*[\s\S]*?\*\//g, '')
269
+ .replace(/^\s*\/\/.*$/gm, '')
270
+ .replace(/,(\s*[}\]])/g, '$1');
271
+ const parsed = JSON.parse(stripped);
272
+ if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) {
273
+ return parsed;
274
+ }
275
+ }
276
+ throw new Error('OpenClaw config is not a JSON object');
277
+ }
278
+ export function mergeOpenclawNotifaiEntry(config, pluginDir, global) {
279
+ const plugins = config.plugins !== null && typeof config.plugins === 'object' && !Array.isArray(config.plugins)
280
+ ? { ...config.plugins }
281
+ : {};
282
+ const entries = plugins.entries !== null && typeof plugins.entries === 'object' && !Array.isArray(plugins.entries)
283
+ ? { ...plugins.entries }
284
+ : {};
285
+ const existing = entries[OPENCLAW_PLUGIN_ID] !== null &&
286
+ typeof entries[OPENCLAW_PLUGIN_ID] === 'object' &&
287
+ !Array.isArray(entries[OPENCLAW_PLUGIN_ID])
288
+ ? { ...entries[OPENCLAW_PLUGIN_ID] }
289
+ : {};
290
+ const hooks = existing.hooks !== null && typeof existing.hooks === 'object' && !Array.isArray(existing.hooks)
291
+ ? { ...existing.hooks }
292
+ : {};
293
+ entries[OPENCLAW_PLUGIN_ID] = {
294
+ ...existing,
295
+ enabled: true,
296
+ hooks: { ...hooks, allowConversationAccess: true },
297
+ };
298
+ plugins.entries = entries;
299
+ if (!global) {
300
+ const load = plugins.load !== null && typeof plugins.load === 'object' && !Array.isArray(plugins.load)
301
+ ? { ...plugins.load }
302
+ : {};
303
+ const paths = Array.isArray(load.paths) ? [...load.paths] : [];
304
+ if (!paths.includes(pluginDir))
305
+ paths.push(pluginDir);
306
+ load.paths = paths;
307
+ plugins.load = load;
308
+ }
309
+ return { ...config, plugins };
310
+ }
311
+ export function removeOpenclawNotifaiEntry(config, pluginDir) {
312
+ const plugins = config.plugins !== null && typeof config.plugins === 'object' && !Array.isArray(config.plugins)
313
+ ? { ...config.plugins }
314
+ : {};
315
+ const entries = plugins.entries !== null && typeof plugins.entries === 'object' && !Array.isArray(plugins.entries)
316
+ ? { ...plugins.entries }
317
+ : {};
318
+ delete entries[OPENCLAW_PLUGIN_ID];
319
+ if (Object.keys(entries).length > 0)
320
+ plugins.entries = entries;
321
+ else
322
+ delete plugins.entries;
323
+ const load = plugins.load !== null && typeof plugins.load === 'object' && !Array.isArray(plugins.load)
324
+ ? { ...plugins.load }
325
+ : {};
326
+ if (Array.isArray(load.paths)) {
327
+ const paths = load.paths.filter((value) => value !== pluginDir);
328
+ if (paths.length > 0)
329
+ load.paths = paths;
330
+ else
331
+ delete load.paths;
332
+ }
333
+ if (Object.keys(load).length > 0)
334
+ plugins.load = load;
335
+ else
336
+ delete plugins.load;
337
+ if (Object.keys(plugins).length > 0)
338
+ return { ...config, plugins };
339
+ const next = { ...config };
340
+ delete next.plugins;
341
+ return next;
342
+ }
343
+ /** Config-directory presence is not ownership. These files mean OpenClaw is actually configured. */
344
+ export function openclawHasGlobalEvidence(exists, env = process.env, platform = process.platform) {
345
+ const state = openclawStateDir(env, platform);
346
+ return (exists(openclawConfigPath(env, platform)) ||
347
+ exists(path.join(state, 'agents')) ||
348
+ exists(path.join(state, 'workspace')));
349
+ }
350
+ //# sourceMappingURL=openclaw-plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openclaw-plugin.js","sourceRoot":"","sources":["../src/openclaw-plugin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ export type OrcaSessionTitleLookup = (env: NodeJS.ProcessEnv) => string | undefined;
2
+ export type OrcaCommand = (executable: string, args: readonly string[]) => string | null;
3
+ /**
4
+ * Read the User-facing task name assigned to this exact Orca worktree.
5
+ *
6
+ * The environment contributes only an opaque selector. Orca must return that
7
+ * exact id and its matching path before its display name becomes trusted; no
8
+ * path component or identifier is ever promoted into an Agent Session label.
9
+ */
10
+ export declare function readOrcaSessionTitle(env: NodeJS.ProcessEnv, command?: OrcaCommand): string | undefined;
@@ -0,0 +1,98 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import path from 'node:path';
3
+ const runOrcaCommand = (executable, args) => {
4
+ try {
5
+ return execFileSync(executable, args, {
6
+ encoding: 'utf8',
7
+ stdio: ['ignore', 'pipe', 'ignore'],
8
+ timeout: 2_000,
9
+ maxBuffer: 512 * 1024,
10
+ windowsHide: true,
11
+ }).trim();
12
+ }
13
+ catch {
14
+ return null;
15
+ }
16
+ };
17
+ function containsControlCharacter(value) {
18
+ return Array.from(value).some((character) => {
19
+ const codePoint = character.codePointAt(0) ?? 0;
20
+ return codePoint < 32 || codePoint === 127;
21
+ });
22
+ }
23
+ function worktreePathFromId(value) {
24
+ if (value.length === 0 || value.length > 4_096 || containsControlCharacter(value))
25
+ return null;
26
+ const separator = value.indexOf('::');
27
+ if (separator <= 0)
28
+ return null;
29
+ const repoId = value.slice(0, separator);
30
+ const worktreePath = value.slice(separator + 2);
31
+ if (!/^[A-Za-z0-9._-]+$/u.test(repoId) || worktreePath.length === 0)
32
+ return null;
33
+ return path.posix.isAbsolute(worktreePath) || path.win32.isAbsolute(worktreePath)
34
+ ? worktreePath
35
+ : null;
36
+ }
37
+ function orcaExecutable(env) {
38
+ const configured = env['ORCA_CLI_COMMAND']?.trim();
39
+ if (configured !== undefined && configured.length > 0) {
40
+ return containsControlCharacter(configured) ? null : configured;
41
+ }
42
+ return env['ORCA_DEV_REPO_ROOT'] === undefined ? 'orca' : 'orca-dev';
43
+ }
44
+ /**
45
+ * Read the User-facing task name assigned to this exact Orca worktree.
46
+ *
47
+ * The environment contributes only an opaque selector. Orca must return that
48
+ * exact id and its matching path before its display name becomes trusted; no
49
+ * path component or identifier is ever promoted into an Agent Session label.
50
+ */
51
+ export function readOrcaSessionTitle(env, command = runOrcaCommand) {
52
+ if (env['TERM_PROGRAM'] !== 'Orca')
53
+ return undefined;
54
+ const worktreeId = env['ORCA_WORKTREE_ID'];
55
+ if (worktreeId === undefined)
56
+ return undefined;
57
+ const expectedPath = worktreePathFromId(worktreeId);
58
+ if (expectedPath === null)
59
+ return undefined;
60
+ const executable = orcaExecutable(env);
61
+ if (executable === null)
62
+ return undefined;
63
+ let output;
64
+ try {
65
+ output = command(executable, [
66
+ 'worktree',
67
+ 'show',
68
+ '--worktree',
69
+ `id:${worktreeId}`,
70
+ '--json',
71
+ ]);
72
+ }
73
+ catch {
74
+ return undefined;
75
+ }
76
+ if (output === null || output === '')
77
+ return undefined;
78
+ let parsed;
79
+ try {
80
+ parsed = JSON.parse(output);
81
+ }
82
+ catch {
83
+ return undefined;
84
+ }
85
+ if (typeof parsed !== 'object' || parsed === null)
86
+ return undefined;
87
+ const response = parsed;
88
+ const worktree = response.result?.worktree;
89
+ if (response.ok !== true ||
90
+ worktree?.id !== worktreeId ||
91
+ worktree.path !== expectedPath ||
92
+ typeof worktree.displayName !== 'string' ||
93
+ worktree.displayName.trim().length === 0) {
94
+ return undefined;
95
+ }
96
+ return worktree.displayName;
97
+ }
98
+ //# sourceMappingURL=orca-session-title.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orca-session-title.js","sourceRoot":"","sources":["../src/orca-session-title.ts"],"names":[],"mappings":"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"}
package/dist/program.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Command } from 'commander';
2
- import { acknowledgeCommand, askCommand, accessStatusCommand, authStatusCommand, capabilitiesCommand, closeCommand, configExplainCommand, configSetCommand, configShowCommand, configUnsetCommand, devicesCommand, doctorCommand, guidanceSetCommand, guidanceShowCommand, guidanceUnsetCommand, hookRunCommand, hooksInstallCommand, hooksUninstallCommand, initCommand, loginCommand, logoutCommand, projectDisableCommand, projectEnableCommand, projectStatusCommand, logsCommand, repliesCommand, sendCommand, statusCommand, type CommandDeps } from './commands.js';
2
+ import { acknowledgeCommand, askCommand, accessStatusCommand, agentSessionRenameCommand, authStatusCommand, capabilitiesCommand, closeCommand, configExplainCommand, configSetCommand, configShowCommand, configUnsetCommand, devicesCommand, doctorCommand, guidanceSetCommand, guidanceShowCommand, guidanceUnsetCommand, hookRunCommand, hooksInstallCommand, hooksUninstallCommand, initCommand, loginCommand, logoutCommand, projectDisableCommand, projectEnableCommand, projectStatusCommand, logsCommand, repliesCommand, sendCommand, soundsCommand, statusCommand, type CommandDeps } from './commands.js';
3
3
  /**
4
4
  * Every command implementation the tree dispatches to, injectable so a test
5
5
  * can parse real argv and assert the exact flags a command would receive —
@@ -14,10 +14,12 @@ declare const defaultRunners: {
14
14
  authStatus: typeof authStatusCommand;
15
15
  accessStatus: typeof accessStatusCommand;
16
16
  devices: typeof devicesCommand;
17
+ sounds: typeof soundsCommand;
17
18
  capabilities: typeof capabilitiesCommand;
18
19
  projectEnable: typeof projectEnableCommand;
19
20
  projectDisable: typeof projectDisableCommand;
20
21
  projectStatus: typeof projectStatusCommand;
22
+ agentSessionRename: typeof agentSessionRenameCommand;
21
23
  send: typeof sendCommand;
22
24
  replies: typeof repliesCommand;
23
25
  acknowledge: typeof acknowledgeCommand;
package/dist/program.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { Command } from 'commander';
3
- import { acknowledgeCommand, askCommand, accessStatusCommand, authStatusCommand, capabilitiesCommand, closeCommand, configExplainCommand, configSetCommand, configShowCommand, configUnsetCommand, devicesCommand, doctorCommand, guidanceSetCommand, guidanceShowCommand, guidanceUnsetCommand, hookDefersDiagnosticsUntilAfterCleanup, hookRunCommand, hooksInstallCommand, hooksUninstallCommand, initCommand, loginCommand, logoutCommand, projectDisableCommand, projectEnableCommand, projectStatusCommand, logsCommand, repliesCommand, reportAskFailure, sendCommand, statusCommand, } from './commands.js';
3
+ import { acknowledgeCommand, askCommand, accessStatusCommand, agentSessionRenameCommand, authStatusCommand, capabilitiesCommand, closeCommand, configExplainCommand, configSetCommand, configShowCommand, configUnsetCommand, devicesCommand, doctorCommand, guidanceSetCommand, guidanceShowCommand, guidanceUnsetCommand, hookDefersDiagnosticsUntilAfterCleanup, hookRunCommand, hooksInstallCommand, hooksUninstallCommand, initCommand, loginCommand, logoutCommand, projectDisableCommand, projectEnableCommand, projectStatusCommand, logsCommand, repliesCommand, reportAskFailure, sendCommand, soundsCommand, statusCommand, } from './commands.js';
4
4
  import { packageVersion } from './release.js';
5
- import { HARNESSES } from './install-hooks.js';
5
+ import { HOOK_INSTALLABLE_HARNESSES } from './harnesses.js';
6
6
  import { GROUP, SEND_GROUP, helpConfiguration, rootHelpFooter } from './ui/help.js';
7
7
  import { readStdinWithTimeout } from './hook-input.js';
8
8
  import { argvFlagNames } from './logging.js';
@@ -46,10 +46,12 @@ const defaultRunners = {
46
46
  authStatus: authStatusCommand,
47
47
  accessStatus: accessStatusCommand,
48
48
  devices: devicesCommand,
49
+ sounds: soundsCommand,
49
50
  capabilities: capabilitiesCommand,
50
51
  projectEnable: projectEnableCommand,
51
52
  projectDisable: projectDisableCommand,
52
53
  projectStatus: projectStatusCommand,
54
+ agentSessionRename: agentSessionRenameCommand,
53
55
  send: sendCommand,
54
56
  replies: repliesCommand,
55
57
  acknowledge: acknowledgeCommand,
@@ -126,11 +128,12 @@ export function buildProgram(deps, options = {}) {
126
128
  /**
127
129
  * Bare `notifai`.
128
130
  *
129
- * At a human terminal this opens the interactive app; anywhere else it prints
130
- * help exactly as before. The check is the same one every other prompt in this
131
- * CLI is gated on — stdin and stdout both TTYs, not CI, `NOTIFAI_NO_INPUT`
132
- * unset — because an agent that reaches a prompt does not fail, it hangs
133
- * for ever waiting on a stdin nobody is typing into.
131
+ * At a human terminal this opens the interactive app, which enters `init`
132
+ * when the machine is not yet paired and otherwise offers daily actions.
133
+ * Anywhere else it prints help exactly as before. The check is the same one
134
+ * every other prompt in this CLI is gated on — stdin and stdout both TTYs,
135
+ * not CI, `NOTIFAI_NO_INPUT` unset — because an agent that reaches a prompt
136
+ * does not fail, it hangs for ever waiting on a stdin nobody is typing into.
134
137
  */
135
138
  /**
136
139
  * An unrecognised subcommand reaches the root as an excess argument, and
@@ -198,9 +201,21 @@ export function buildProgram(deps, options = {}) {
198
201
  project.command('status').description('Show lifecycle enablement for this Project').option('--json').action((opts) => {
199
202
  exit(runners.projectStatus(deps, opts.json === true));
200
203
  });
204
+ const session = program
205
+ .command('session')
206
+ .helpGroup(GROUP.agent)
207
+ .summary('Inspect or rename the current Agent Session')
208
+ .description('Operate on the exact Agent Session owned by the active harness');
209
+ session
210
+ .command('rename <label>')
211
+ .description('Rename this exact Agent Session; agents use this only after its job changes completely enough that the old label would mislead')
212
+ .option('--json', 'machine-readable output')
213
+ .action(async (label, opts) => {
214
+ exit(await runners.agentSessionRename(deps, label, opts));
215
+ });
201
216
  program
202
217
  .command('login')
203
- .helpGroup(GROUP.start)
218
+ .helpGroup(GROUP.advanced)
204
219
  .summary('Sign in and pair this machine')
205
220
  .description('Pair this machine with your Notifai account via browser approval')
206
221
  .option('--name <name>', 'machine name shown in the dashboard (default: hostname)')
@@ -246,6 +261,15 @@ export function buildProgram(deps, options = {}) {
246
261
  .action(async (opts) => {
247
262
  exit(await runners.devices(deps, opts));
248
263
  });
264
+ program
265
+ .command('sounds')
266
+ .helpGroup(GROUP.daily)
267
+ .summary('List shipped and Account custom sounds')
268
+ .description('List the sounds --sound and the saved sound key accept: shipped names and this Account\'s custom sounds')
269
+ .option('--json', 'machine-readable output')
270
+ .action(async (opts) => {
271
+ exit(await runners.sounds(deps, opts));
272
+ });
249
273
  program
250
274
  .command('capabilities')
251
275
  .helpGroup(GROUP.advanced)
@@ -289,7 +313,7 @@ export function buildProgram(deps, options = {}) {
289
313
  .option('--choice <label>', 'with --reply, ask a closed question; repeat the flag once per answer (2-6)', (v, all = []) => [...all, v])
290
314
  .option('--multi', 'with --choice, several answers may be selected')
291
315
  .optionsGroup(SEND_GROUP.advanced)
292
- .option('--sound <sound>', 'override saved sound: default | done | attention | alert | none')
316
+ .option('--sound <sound>', 'override saved sound: default | done | attention | alert | none, or a custom name or id from notifai sounds')
293
317
  .option('--level <level>', 'Apple-only interruption level: passive | active | time_sensitive (unsupported with --platform android)')
294
318
  .option('--wait <seconds>', 'how long to wait for provider outcomes', (v) => Number(v))
295
319
  .option('--no-wait', 'return immediately after acceptance')
@@ -330,7 +354,7 @@ export function buildProgram(deps, options = {}) {
330
354
  flags.wait = wait;
331
355
  exit(await runners.send(deps, flags));
332
356
  });
333
- send.addHelpText('after', `\nKind is required, and it selects the sound the notification arrives with: done rings the completion chime, failed the most insistent tone, blocked and question a distinct attention tone, update the standard one.\nAn explicit --sound or saved sound preference outranks that default. --level is Apple-only; Android attention is owned by kind, product channels, and device settings.\n`);
357
+ send.addHelpText('after', `\nKind is required, and it selects the sound the notification arrives with: done rings the completion chime, failed the most insistent tone, blocked and question a distinct attention tone, update Device default.\nAn explicit --sound or saved sound preference outranks that default. --sound accepts a shipped name, an Account custom sound name or id from \`notifai sounds\`, or none. --level is Apple-only; Android attention is owned by kind, product channels, and device settings.\n`);
334
358
  program
335
359
  .command('replies [request_id]')
336
360
  .helpGroup(GROUP.agent)
@@ -449,7 +473,7 @@ export function buildProgram(deps, options = {}) {
449
473
  .option('--owner <name>', 'internal ownership marker')
450
474
  .option('--harness <name>', 'internal harness output adapter')
451
475
  .action(async (event, opts) => {
452
- const harness = HARNESSES.find((candidate) => candidate === opts.harness);
476
+ const harness = HOOK_INSTALLABLE_HARNESSES.find((candidate) => candidate === opts.harness);
453
477
  exit(await runners.hookRun(deps, event, () => readStdinWithTimeout(), harness));
454
478
  });
455
479
  const hooks = program
@@ -460,7 +484,7 @@ export function buildProgram(deps, options = {}) {
460
484
  hooks
461
485
  .command('install')
462
486
  .description('Wire this harness to route registered questions to your devices')
463
- .option('--harness <name>', 'claude-code | codex | cursor | opencode (default: every detected harness)')
487
+ .option('--harness <name>', 'claude-code | codex | cursor | opencode | openclaw (default: every detected harness)')
464
488
  .option('--global', 'install for every project instead of just this one')
465
489
  .action((opts) => {
466
490
  exit(runners.hooksInstall(deps, opts));
@@ -468,7 +492,7 @@ export function buildProgram(deps, options = {}) {
468
492
  hooks
469
493
  .command('uninstall')
470
494
  .description('Remove the hooks this CLI installed')
471
- .option('--harness <name>', 'claude-code | codex | cursor | opencode (default: detected)')
495
+ .option('--harness <name>', 'claude-code | codex | cursor | opencode | openclaw (default: detected)')
472
496
  .option('--global', 'remove the machine-wide install')
473
497
  .action((opts) => {
474
498
  exit(runners.hooksUninstall(deps, opts));