@raidiant/notifai 6.0.0 → 7.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/dist/atomic-file.js +13 -0
- package/dist/atomic-file.js.map +1 -1
- package/dist/commands-core.d.ts +3 -0
- package/dist/commands-core.js +23 -4
- package/dist/commands-core.js.map +1 -1
- package/dist/commands-doctor.js +7 -5
- package/dist/commands-doctor.js.map +1 -1
- package/dist/commands-harness-context.d.ts +4 -0
- package/dist/commands-harness-context.js +4 -0
- package/dist/commands-harness-context.js.map +1 -1
- package/dist/commands-init.js +0 -1
- package/dist/commands-init.js.map +1 -1
- package/dist/commands-send-support.js +22 -0
- package/dist/commands-send-support.js.map +1 -1
- package/dist/config.d.ts +3 -3
- package/dist/config.js +3 -3
- package/dist/generated-session-label.d.ts +2 -0
- package/dist/generated-session-label.js +152 -0
- package/dist/generated-session-label.js.map +1 -0
- package/dist/invocation-context.d.ts +3 -0
- package/dist/invocation-context.js +23 -6
- package/dist/invocation-context.js.map +1 -1
- package/dist/main.js +2 -2
- package/dist/main.js.map +1 -1
- package/dist/opencode-plugin.js +18 -3
- package/dist/opencode-plugin.js.map +1 -1
- package/dist/orca-session-title.d.ts +10 -0
- package/dist/orca-session-title.js +99 -0
- package/dist/orca-session-title.js.map +1 -0
- package/dist/session-labels.d.ts +30 -0
- package/dist/session-labels.js +278 -0
- package/dist/session-labels.js.map +1 -0
- package/package.json +2 -2
- package/src/atomic-file.ts +12 -0
- package/src/commands-core.ts +24 -4
- package/src/commands-doctor.ts +7 -6
- package/src/commands-harness-context.ts +8 -0
- package/src/commands-init.ts +0 -1
- package/src/commands-send-support.ts +27 -0
- package/src/commands.test.ts +203 -6
- package/src/config.ts +3 -3
- package/src/generated-session-label.ts +155 -0
- package/src/hooks.test.ts +4 -5
- package/src/install-hooks.test.ts +84 -1
- package/src/invocation-context.test.ts +115 -16
- package/src/invocation-context.ts +32 -11
- package/src/main.ts +8 -2
- package/src/opencode-plugin.ts +18 -3
- package/src/orca-session-title.test.ts +95 -0
- package/src/orca-session-title.ts +105 -0
- package/src/session-labels.test.ts +324 -0
- package/src/session-labels.ts +360 -0
- package/src/skill.test.ts +15 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { SESSION_LABEL_MAX_LENGTH } from '@raidiant/notifai-protocol';
|
|
5
|
+
import { atomicWriteFileSync } from './atomic-file.js';
|
|
6
|
+
import { stateDir } from './config.js';
|
|
7
|
+
import { withFileLock } from './file-lock.js';
|
|
8
|
+
import { generatedSessionLabel } from './generated-session-label.js';
|
|
9
|
+
import { HARNESS_LABELS } from './harnesses.js';
|
|
10
|
+
const STORE_VERSION = 1;
|
|
11
|
+
const MONTHS = [
|
|
12
|
+
'Jan',
|
|
13
|
+
'Feb',
|
|
14
|
+
'Mar',
|
|
15
|
+
'Apr',
|
|
16
|
+
'May',
|
|
17
|
+
'Jun',
|
|
18
|
+
'Jul',
|
|
19
|
+
'Aug',
|
|
20
|
+
'Sep',
|
|
21
|
+
'Oct',
|
|
22
|
+
'Nov',
|
|
23
|
+
'Dec',
|
|
24
|
+
];
|
|
25
|
+
function storePath(env) {
|
|
26
|
+
return path.join(stateDir(env), 'session-labels.json');
|
|
27
|
+
}
|
|
28
|
+
/** The state key cannot reveal the harness's opaque session identifier. */
|
|
29
|
+
function sessionKey(sessionId) {
|
|
30
|
+
return createHash('sha256').update(sessionId).digest('hex');
|
|
31
|
+
}
|
|
32
|
+
function emptyStore() {
|
|
33
|
+
return { version: STORE_VERSION, sessions: {} };
|
|
34
|
+
}
|
|
35
|
+
function storedRecord(candidate) {
|
|
36
|
+
if (typeof candidate !== 'object' || candidate === null)
|
|
37
|
+
return null;
|
|
38
|
+
const value = candidate;
|
|
39
|
+
if (typeof value.label !== 'string' ||
|
|
40
|
+
value.label.length === 0 ||
|
|
41
|
+
value.label.length > SESSION_LABEL_MAX_LENGTH ||
|
|
42
|
+
!['explicit', 'harness', 'fallback'].includes(value.source ?? '') ||
|
|
43
|
+
typeof value.first_seen_at !== 'number' ||
|
|
44
|
+
!Number.isFinite(value.first_seen_at)) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const harness = value.harness;
|
|
48
|
+
if (harness !== undefined && !Object.hasOwn(HARNESS_LABELS, harness))
|
|
49
|
+
return null;
|
|
50
|
+
return {
|
|
51
|
+
label: value.label,
|
|
52
|
+
source: value.source,
|
|
53
|
+
first_seen_at: value.first_seen_at,
|
|
54
|
+
...(harness === undefined ? {} : { harness }),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function readStore(file) {
|
|
58
|
+
if (!existsSync(file))
|
|
59
|
+
return emptyStore();
|
|
60
|
+
let parsed;
|
|
61
|
+
try {
|
|
62
|
+
parsed = JSON.parse(readFileSync(file, 'utf8'));
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
throw new Error(`the session-name store is unreadable: ${err instanceof Error ? err.message : String(err)}`);
|
|
66
|
+
}
|
|
67
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
68
|
+
throw new Error('the session-name store is not a JSON object');
|
|
69
|
+
}
|
|
70
|
+
const raw = parsed;
|
|
71
|
+
if (raw.version !== STORE_VERSION) {
|
|
72
|
+
throw new Error(`unsupported session-name store version ${String(raw.version)}`);
|
|
73
|
+
}
|
|
74
|
+
if (typeof raw.sessions !== 'object' || raw.sessions === null) {
|
|
75
|
+
throw new Error('the session-name store has no sessions object');
|
|
76
|
+
}
|
|
77
|
+
const sessions = {};
|
|
78
|
+
for (const [key, candidate] of Object.entries(raw.sessions)) {
|
|
79
|
+
const record = storedRecord(candidate);
|
|
80
|
+
if (!/^[a-f0-9]{64}$/.test(key) || record === null) {
|
|
81
|
+
throw new Error('the session-name store contains an invalid record');
|
|
82
|
+
}
|
|
83
|
+
sessions[key] = record;
|
|
84
|
+
}
|
|
85
|
+
return { version: STORE_VERSION, sessions };
|
|
86
|
+
}
|
|
87
|
+
function writeStore(file, store) {
|
|
88
|
+
atomicWriteFileSync(file, `${JSON.stringify(store, null, 2)}\n`);
|
|
89
|
+
}
|
|
90
|
+
function normalizeWhitespace(value) {
|
|
91
|
+
return value.trim().replaceAll(/\s+/gu, ' ');
|
|
92
|
+
}
|
|
93
|
+
/** Match the same UTF-16 length TypeBox enforces at the wire boundary. */
|
|
94
|
+
function truncate(value, maxLength) {
|
|
95
|
+
if (value.length <= maxLength)
|
|
96
|
+
return value;
|
|
97
|
+
let prefix = value.slice(0, Math.max(0, maxLength - 1));
|
|
98
|
+
const last = prefix.charCodeAt(prefix.length - 1);
|
|
99
|
+
if (last >= 0xd800 && last <= 0xdbff)
|
|
100
|
+
prefix = prefix.slice(0, -1);
|
|
101
|
+
return `${prefix}…`;
|
|
102
|
+
}
|
|
103
|
+
const UUID_PATTERN = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/iu;
|
|
104
|
+
const HASH_PATTERN = /\b[0-9a-f]{12,}\b/iu;
|
|
105
|
+
const HARNESS_ID_PATTERN = /\b(?:ses|thread)_[A-Za-z0-9_-]{8,}\b/iu;
|
|
106
|
+
const FILESYSTEM_PATH_PATTERN = /(?:^|[\s("'=:])(?:~[\\/]|[A-Za-z]:[\\/]|\\\\|\/(?:Users|home|private|tmp|var|Volumes|opt|etc|usr|workspace|workspaces)(?:[\\/]|$))/u;
|
|
107
|
+
const OPENCODE_PENDING_TITLE = /^New session(?:\s*-\s*\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z)?$/iu;
|
|
108
|
+
function containsPrivatePath(value, env) {
|
|
109
|
+
if (FILESYSTEM_PATH_PATTERN.test(value))
|
|
110
|
+
return true;
|
|
111
|
+
const folded = value.toLocaleLowerCase('en-US');
|
|
112
|
+
for (const key of ['HOME', 'USERPROFILE', 'TMPDIR']) {
|
|
113
|
+
const candidate = env[key]?.trim().replace(/[\\/]+$/u, '');
|
|
114
|
+
if (candidate !== undefined &&
|
|
115
|
+
candidate.length > 1 &&
|
|
116
|
+
folded.includes(candidate.toLocaleLowerCase('en-US'))) {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
function violatesLabelPrivacy(value, input) {
|
|
123
|
+
const sessionId = input.sessionId.trim();
|
|
124
|
+
const exposesCurrentId = sessionId.length > 0 &&
|
|
125
|
+
(value === sessionId || (sessionId.length >= 8 && value.includes(sessionId)));
|
|
126
|
+
return (exposesCurrentId ||
|
|
127
|
+
UUID_PATTERN.test(value) ||
|
|
128
|
+
HASH_PATTERN.test(value) ||
|
|
129
|
+
HARNESS_ID_PATTERN.test(value) ||
|
|
130
|
+
containsPrivatePath(value, input.env));
|
|
131
|
+
}
|
|
132
|
+
function explicitCandidate(value, input) {
|
|
133
|
+
if (value === undefined)
|
|
134
|
+
return null;
|
|
135
|
+
const normalized = normalizeWhitespace(value);
|
|
136
|
+
if (normalized.length === 0) {
|
|
137
|
+
return { error: '--session-label (or NOTIFAI_SESSION_LABEL) must not be empty.' };
|
|
138
|
+
}
|
|
139
|
+
if (normalized.length > SESSION_LABEL_MAX_LENGTH) {
|
|
140
|
+
return {
|
|
141
|
+
error: `--session-label (or NOTIFAI_SESSION_LABEL) must be at most ${SESSION_LABEL_MAX_LENGTH} characters.`,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
if (violatesLabelPrivacy(normalized, input)) {
|
|
145
|
+
return {
|
|
146
|
+
error: '--session-label (or NOTIFAI_SESSION_LABEL) must not contain a session identifier, hash, or filesystem path.',
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return { label: normalized, source: 'explicit' };
|
|
150
|
+
}
|
|
151
|
+
function harnessLabelIsPending(value, input) {
|
|
152
|
+
if (input.harnessLabelPending === true)
|
|
153
|
+
return true;
|
|
154
|
+
if (input.harness !== 'opencode' || value === undefined)
|
|
155
|
+
return false;
|
|
156
|
+
return OPENCODE_PENDING_TITLE.test(normalizeWhitespace(value));
|
|
157
|
+
}
|
|
158
|
+
function harnessCandidate(value, input) {
|
|
159
|
+
if (value === undefined)
|
|
160
|
+
return null;
|
|
161
|
+
const normalized = normalizeWhitespace(value);
|
|
162
|
+
if (normalized.length === 0 || violatesLabelPrivacy(normalized, input))
|
|
163
|
+
return null;
|
|
164
|
+
return {
|
|
165
|
+
label: truncate(normalized, SESSION_LABEL_MAX_LENGTH),
|
|
166
|
+
source: 'harness',
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function pad(value) {
|
|
170
|
+
return String(value).padStart(2, '0');
|
|
171
|
+
}
|
|
172
|
+
/** Stable English shape using the machine's local calendar and clock. */
|
|
173
|
+
export function formatSessionFirstSeen(now) {
|
|
174
|
+
const date = new Date(now);
|
|
175
|
+
return `${MONTHS[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
176
|
+
}
|
|
177
|
+
function fallbackCandidate(sessionId) {
|
|
178
|
+
return {
|
|
179
|
+
label: generatedSessionLabel(sessionId),
|
|
180
|
+
source: 'fallback',
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function isLegacyDateFallback(record) {
|
|
184
|
+
if (record.source !== 'fallback')
|
|
185
|
+
return false;
|
|
186
|
+
const owner = record.harness === undefined ? 'Agent' : HARNESS_LABELS[record.harness];
|
|
187
|
+
return record.label.startsWith(`${owner} session · `);
|
|
188
|
+
}
|
|
189
|
+
function withSuffix(base, suffix) {
|
|
190
|
+
const room = SESSION_LABEL_MAX_LENGTH - suffix.length;
|
|
191
|
+
return `${truncate(base, room)}${suffix}`;
|
|
192
|
+
}
|
|
193
|
+
function collisionKey(value) {
|
|
194
|
+
return value.normalize('NFKC').toLocaleLowerCase('en-US');
|
|
195
|
+
}
|
|
196
|
+
function uniqueLabel(base, source, harness, now, used) {
|
|
197
|
+
if (!used.has(collisionKey(base)))
|
|
198
|
+
return base;
|
|
199
|
+
if (source === 'fallback') {
|
|
200
|
+
for (let ordinal = 2;; ordinal += 1) {
|
|
201
|
+
const candidate = withSuffix(base, ` · ${ordinal}`);
|
|
202
|
+
if (!used.has(collisionKey(candidate)))
|
|
203
|
+
return candidate;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const owner = harness === undefined ? 'Agent' : HARNESS_LABELS[harness];
|
|
207
|
+
const ownerCandidate = withSuffix(base, ` · ${owner}`);
|
|
208
|
+
if (!used.has(collisionKey(ownerCandidate)))
|
|
209
|
+
return ownerCandidate;
|
|
210
|
+
const timed = withSuffix(base, ` · ${owner} · ${formatSessionFirstSeen(now)}`);
|
|
211
|
+
if (!used.has(collisionKey(timed)))
|
|
212
|
+
return timed;
|
|
213
|
+
for (let ordinal = 2;; ordinal += 1) {
|
|
214
|
+
const candidate = withSuffix(timed, ` · ${ordinal}`);
|
|
215
|
+
if (!used.has(collisionKey(candidate)))
|
|
216
|
+
return candidate;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Freeze the first accepted human name for one immutable session.
|
|
221
|
+
*
|
|
222
|
+
* Explicit agent/User input wins, then a title supplied by a trusted harness
|
|
223
|
+
* adapter, then a generated fallback. Later sends reuse the frozen value
|
|
224
|
+
* even when their branch, worktree, title candidate, or notification changes.
|
|
225
|
+
*/
|
|
226
|
+
export function resolveSessionLabel(input) {
|
|
227
|
+
const now = input.now ?? Date.now();
|
|
228
|
+
const file = storePath(input.env);
|
|
229
|
+
const key = sessionKey(input.sessionId);
|
|
230
|
+
try {
|
|
231
|
+
return withFileLock(`${file}.lock`, () => {
|
|
232
|
+
const store = readStore(file);
|
|
233
|
+
const existing = store.sessions[key];
|
|
234
|
+
if (existing !== undefined) {
|
|
235
|
+
if (!isLegacyDateFallback(existing)) {
|
|
236
|
+
return { ok: true, label: existing.label, source: existing.source };
|
|
237
|
+
}
|
|
238
|
+
const used = new Set(Object.entries(store.sessions)
|
|
239
|
+
.filter(([storedKey]) => storedKey !== key)
|
|
240
|
+
.map(([, record]) => collisionKey(record.label)));
|
|
241
|
+
const label = uniqueLabel(generatedSessionLabel(input.sessionId), 'fallback', input.harness ?? existing.harness, now, used);
|
|
242
|
+
store.sessions[key] = { ...existing, label };
|
|
243
|
+
writeStore(file, store);
|
|
244
|
+
return { ok: true, label, source: existing.source };
|
|
245
|
+
}
|
|
246
|
+
const explicit = explicitCandidate(input.explicitLabel, input);
|
|
247
|
+
if (explicit !== null && 'error' in explicit) {
|
|
248
|
+
return { ok: false, error: explicit.error };
|
|
249
|
+
}
|
|
250
|
+
const nativePending = harnessLabelIsPending(input.harnessLabel, input);
|
|
251
|
+
const native = nativePending ? null : harnessCandidate(input.harnessLabel, input);
|
|
252
|
+
if (explicit === null && nativePending) {
|
|
253
|
+
return {
|
|
254
|
+
ok: false,
|
|
255
|
+
error: "OpenCode is still generating this session's title; retry shortly or pass --session-label.",
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
const candidate = explicit ?? native ?? fallbackCandidate(input.sessionId);
|
|
259
|
+
const used = new Set(Object.values(store.sessions).map((record) => collisionKey(record.label)));
|
|
260
|
+
const label = uniqueLabel(candidate.label, candidate.source, input.harness, now, used);
|
|
261
|
+
store.sessions[key] = {
|
|
262
|
+
label,
|
|
263
|
+
source: candidate.source,
|
|
264
|
+
first_seen_at: now,
|
|
265
|
+
...(input.harness === undefined ? {} : { harness: input.harness }),
|
|
266
|
+
};
|
|
267
|
+
writeStore(file, store);
|
|
268
|
+
return { ok: true, label, source: candidate.source };
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
catch (err) {
|
|
272
|
+
return {
|
|
273
|
+
ok: false,
|
|
274
|
+
error: `Could not persist this session's name: ${err instanceof Error ? err.message : String(err)}`,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
//# sourceMappingURL=session-labels.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-labels.js","sourceRoot":"","sources":["../src/session-labels.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@raidiant/notifai",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Send native phone notifications from agents and local programs",
|
|
6
6
|
"bin": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@clack/prompts": "^1.7.0",
|
|
24
|
-
"@raidiant/notifai-protocol": "
|
|
24
|
+
"@raidiant/notifai-protocol": "4.0.0",
|
|
25
25
|
"commander": "^14.0.0",
|
|
26
26
|
"picocolors": "^1.1.1",
|
|
27
27
|
"smol-toml": "^1.4.0"
|
package/src/atomic-file.ts
CHANGED
|
@@ -57,6 +57,7 @@ export function atomicWriteFileSync(
|
|
|
57
57
|
assertSameDirectory(directory, parent)
|
|
58
58
|
assertUnchangedTarget(file, target)
|
|
59
59
|
renameSync(temp, file)
|
|
60
|
+
syncDirectory(directory)
|
|
60
61
|
} catch (err) {
|
|
61
62
|
if (handle !== undefined) closeSync(handle)
|
|
62
63
|
try {
|
|
@@ -68,6 +69,17 @@ export function atomicWriteFileSync(
|
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
/** Persist the published directory entry where the host supports directory fsync. */
|
|
73
|
+
function syncDirectory(directory: string): void {
|
|
74
|
+
if (process.platform === 'win32') return
|
|
75
|
+
const handle = openSync(directory, 'r')
|
|
76
|
+
try {
|
|
77
|
+
fsyncSync(handle)
|
|
78
|
+
} finally {
|
|
79
|
+
closeSync(handle)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
71
83
|
/** Mode to preserve, or the mode for a new file. Refuses a non-regular target. */
|
|
72
84
|
interface TargetMetadata {
|
|
73
85
|
mode: number
|
package/src/commands-core.ts
CHANGED
|
@@ -13,6 +13,7 @@ import type { CredentialStore, MachineCredential } from './credentials.js'
|
|
|
13
13
|
import { type HookAdapterTarget } from './hook-adapter.js'
|
|
14
14
|
import { logConfigResolved, logSettingsFrom, nullLogger, type Logger } from './logging.js'
|
|
15
15
|
import type { NativeSkills } from './native-skills.js'
|
|
16
|
+
import type { OrcaSessionTitleLookup } from './orca-session-title.js'
|
|
16
17
|
import { packageVersion } from './release.js'
|
|
17
18
|
import type { Tone } from './ui/theme.js'
|
|
18
19
|
|
|
@@ -81,6 +82,8 @@ export interface CommandDeps {
|
|
|
81
82
|
codexSourcePid?: number
|
|
82
83
|
/** Test seam and production adapter for the external native skills installer. */
|
|
83
84
|
nativeSkills?: NativeSkills
|
|
85
|
+
/** Test seam for Orca's exact-worktree semantic title lookup. */
|
|
86
|
+
orcaSessionTitle?: OrcaSessionTitleLookup
|
|
84
87
|
/**
|
|
85
88
|
* The local record of what this invocation did. Optional so a test fake need
|
|
86
89
|
* not carry one; `log(deps)` supplies a logger that records nothing.
|
|
@@ -151,12 +154,29 @@ export function authedClient(deps: CommandDeps, config: CliConfig): { client: Ap
|
|
|
151
154
|
|
|
152
155
|
export const UPDATE_CLI_COMMAND = 'npm install -g @raidiant/notifai'
|
|
153
156
|
|
|
154
|
-
function
|
|
157
|
+
function affectedDeviceNames(details: unknown): string[] {
|
|
158
|
+
if (typeof details !== 'object' || details === null || !('device_names' in details)) return []
|
|
159
|
+
const names = (details as { device_names?: unknown }).device_names
|
|
160
|
+
if (!Array.isArray(names)) return []
|
|
161
|
+
return [...new Set(names.filter((name): name is string => typeof name === 'string' && name.trim() !== ''))]
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function naturalList(values: readonly string[]): string {
|
|
165
|
+
if (values.length <= 1) return values[0] ?? ''
|
|
166
|
+
if (values.length === 2) return `${values[0]} and ${values[1]}`
|
|
167
|
+
return `${values.slice(0, -1).join(', ')}, and ${values.at(-1)}`
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function localRecovery(action: RecoveryAction | null, details: unknown): string | null {
|
|
155
171
|
switch (action) {
|
|
156
172
|
case 'update_cli':
|
|
157
173
|
return `next: ${UPDATE_CLI_COMMAND}`
|
|
158
|
-
case 'update_companion':
|
|
159
|
-
|
|
174
|
+
case 'update_companion': {
|
|
175
|
+
const names = affectedDeviceNames(details)
|
|
176
|
+
return names.length > 0
|
|
177
|
+
? `next: Update Notifai on ${naturalList(names)}.`
|
|
178
|
+
: 'next: Update Notifai on the affected devices.'
|
|
179
|
+
}
|
|
160
180
|
case 'wait_for_service':
|
|
161
181
|
return 'next: The service is being updated; try again later.'
|
|
162
182
|
default:
|
|
@@ -213,7 +233,7 @@ export function reportError(
|
|
|
213
233
|
'disagree about the contract; check with `notifai doctor`',
|
|
214
234
|
)
|
|
215
235
|
}
|
|
216
|
-
const recovery = localRecovery(err.recoveryAction)
|
|
236
|
+
const recovery = localRecovery(err.recoveryAction, err.details)
|
|
217
237
|
if (recovery !== null) deps.io.err(recovery)
|
|
218
238
|
else if (err.recoveryAction === null && err.nextAction) deps.io.err(`next: ${err.nextAction}`)
|
|
219
239
|
if (err.code === 'auth_required' || err.code === 'machine_revoked') return EXIT.auth
|
package/src/commands-doctor.ts
CHANGED
|
@@ -589,13 +589,14 @@ export async function doctorCommand(
|
|
|
589
589
|
deps.io.out(`${mark} ${line(s)}`)
|
|
590
590
|
}
|
|
591
591
|
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
? 'Everything looks good'
|
|
595
|
-
: blocker?.id === 'contract'
|
|
596
|
-
? 'Update Notifai, then run doctor again'
|
|
597
|
-
: `Start with: ${remedyLine(blocker)}`,
|
|
592
|
+
const surfacedUpdate = readiness.states.some(
|
|
593
|
+
(state) => state.id === 'contract' && (state.status === 'gap' || state.status === 'optional-gap'),
|
|
598
594
|
)
|
|
595
|
+
if (blocker !== null && blocker.id !== 'contract') {
|
|
596
|
+
await deps.io.outro?.(`Start with: ${remedyLine(blocker)}`)
|
|
597
|
+
} else if (!surfacedUpdate) {
|
|
598
|
+
await deps.io.outro?.('Everything looks good')
|
|
599
|
+
}
|
|
599
600
|
return ok ? EXIT.ok : EXIT.failed
|
|
600
601
|
}
|
|
601
602
|
|
|
@@ -12,6 +12,10 @@ export interface ActiveHarnessSession {
|
|
|
12
12
|
harness: Harness
|
|
13
13
|
label: string
|
|
14
14
|
sessionId?: string
|
|
15
|
+
/** Trusted human title published by this harness's managed adapter. */
|
|
16
|
+
sessionLabel?: string
|
|
17
|
+
/** The harness has only a temporary placeholder title so far. */
|
|
18
|
+
sessionLabelPending?: boolean
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
/**
|
|
@@ -26,10 +30,14 @@ function harnessEnvCandidates(env: NodeJS.ProcessEnv): ActiveHarnessSession[] {
|
|
|
26
30
|
const candidates: ActiveHarnessSession[] = []
|
|
27
31
|
if (env['NOTIFAI_ACTIVE_HARNESS'] === 'opencode') {
|
|
28
32
|
const sessionId = env['NOTIFAI_ACTIVE_SESSION_ID']
|
|
33
|
+
const sessionLabel = env['NOTIFAI_ACTIVE_SESSION_LABEL']
|
|
34
|
+
const sessionLabelPending = env['NOTIFAI_ACTIVE_SESSION_LABEL_PENDING'] === '1'
|
|
29
35
|
candidates.push({
|
|
30
36
|
harness: 'opencode',
|
|
31
37
|
label: 'OpenCode',
|
|
32
38
|
...(sessionId === undefined || sessionId === '' ? {} : { sessionId }),
|
|
39
|
+
...(sessionLabel === undefined || sessionLabel === '' ? {} : { sessionLabel }),
|
|
40
|
+
...(sessionLabelPending ? { sessionLabelPending: true } : {}),
|
|
33
41
|
})
|
|
34
42
|
}
|
|
35
43
|
if (env['CLAUDECODE'] === '1') {
|
package/src/commands-init.ts
CHANGED
|
@@ -4,16 +4,40 @@ import { existsSync, readFileSync } from 'node:fs'
|
|
|
4
4
|
import path from 'node:path'
|
|
5
5
|
import { isRetryableReplyPollError, type ApiClient } from './client.js'
|
|
6
6
|
import { buildSourceContext, inferInvocationContext } from './invocation-context.js'
|
|
7
|
+
import { readOrcaSessionTitle } from './orca-session-title.js'
|
|
7
8
|
import { type DraftInvocation, type SendFlags } from './send.js'
|
|
8
9
|
import { EXIT, reportError, type CommandDeps } from './commands-core.js'
|
|
9
10
|
import type { ActiveHarnessSession } from './commands-harness-context.js'
|
|
10
11
|
|
|
12
|
+
function managedSessionTitle(
|
|
13
|
+
deps: CommandDeps,
|
|
14
|
+
flags: Pick<SendFlags, 'sessionLabel'>,
|
|
15
|
+
active: ActiveHarnessSession | null,
|
|
16
|
+
): string | undefined {
|
|
17
|
+
if (active?.sessionLabel !== undefined) return active.sessionLabel
|
|
18
|
+
if (
|
|
19
|
+
active?.harness !== 'claude-code' ||
|
|
20
|
+
active.sessionId === undefined ||
|
|
21
|
+
active.sessionId !== deps.env['CLAUDE_CODE_SESSION_ID'] ||
|
|
22
|
+
flags.sessionLabel !== undefined ||
|
|
23
|
+
deps.env['NOTIFAI_SESSION_LABEL'] !== undefined
|
|
24
|
+
) {
|
|
25
|
+
return undefined
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
return (deps.orcaSessionTitle ?? readOrcaSessionTitle)(deps.env)
|
|
29
|
+
} catch {
|
|
30
|
+
return undefined
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
11
34
|
export function resolveDraftInvocation(
|
|
12
35
|
deps: CommandDeps,
|
|
13
36
|
flags: Pick<SendFlags, 'sessionId' | 'sessionLabel'>,
|
|
14
37
|
active: ActiveHarnessSession | null,
|
|
15
38
|
): { ok: true; invocation: DraftInvocation } | { ok: false; error: string } {
|
|
16
39
|
const inferred = inferInvocationContext(deps.cwd)
|
|
40
|
+
const sessionTitle = managedSessionTitle(deps, flags, active)
|
|
17
41
|
const source = buildSourceContext({
|
|
18
42
|
env: deps.env,
|
|
19
43
|
invocation: inferred,
|
|
@@ -25,8 +49,11 @@ export function resolveDraftInvocation(
|
|
|
25
49
|
activeHarness: {
|
|
26
50
|
harness: active.harness,
|
|
27
51
|
...(active.sessionId === undefined ? {} : { sessionId: active.sessionId }),
|
|
52
|
+
...(sessionTitle === undefined ? {} : { sessionLabel: sessionTitle }),
|
|
53
|
+
...(active.sessionLabelPending === true ? { sessionLabelPending: true } : {}),
|
|
28
54
|
},
|
|
29
55
|
}),
|
|
56
|
+
now: (deps.now ?? Date.now)(),
|
|
30
57
|
})
|
|
31
58
|
if (!source.ok) return source
|
|
32
59
|
return {
|