stitchkit 0.95.0 → 0.95.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -0
- package/dist/application/directory-inbox-contract.d.ts +129 -0
- package/dist/application/directory-inbox-contract.d.ts.map +1 -0
- package/dist/application/directory-inbox.d.ts +3 -0
- package/dist/application/directory-inbox.d.ts.map +1 -0
- package/dist/entrypoints/application/directory-inbox.d.ts +11 -0
- package/dist/entrypoints/application/directory-inbox.d.ts.map +1 -0
- package/dist/entrypoints/application/directory-inbox.js +221 -0
- package/dist/entrypoints/application.d.ts +1 -0
- package/dist/entrypoints/application.d.ts.map +1 -1
- package/dist/entrypoints/application.js +6 -0
- package/dist/entrypoints/server.js +6 -219
- package/dist/entrypoints/voice.d.ts +6 -0
- package/dist/entrypoints/voice.d.ts.map +1 -0
- package/dist/entrypoints/voice.js +221 -0
- package/dist/index-5jrd0yng.js +114 -0
- package/dist/index-tm88avnj.js +221 -0
- package/dist/voice/phases.d.ts +4 -0
- package/dist/voice/phases.d.ts.map +1 -0
- package/dist/voice/sentences.d.ts +8 -0
- package/dist/voice/sentences.d.ts.map +1 -0
- package/dist/voice/speakable.d.ts +2 -0
- package/dist/voice/speakable.d.ts.map +1 -0
- package/dist/voice/speech-queue.d.ts +42 -0
- package/dist/voice/speech-queue.d.ts.map +1 -0
- package/llms/application-directory-inbox.txt +9 -0
- package/llms/application.2.txt +2 -0
- package/llms/application.txt +30 -1
- package/llms/stitchkit.txt +2 -0
- package/llms/voice.txt +106 -0
- package/llms-full.txt +131 -1
- package/llms.txt +5 -2
- package/package.json +9 -1
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isRecord
|
|
3
|
+
} from "./index-hffjpmq9.js";
|
|
4
|
+
|
|
5
|
+
// src/application/file-state-store.ts
|
|
6
|
+
import { mkdir, open, readdir, readFile, rename, stat, unlink } from "node:fs/promises";
|
|
7
|
+
import { basename, dirname, join } from "node:path";
|
|
8
|
+
var ABANDONED_LOCK_MULTIPLIER = 10;
|
|
9
|
+
var ABANDONED_HEARTBEATS = 30;
|
|
10
|
+
var MIN_HEARTBEAT_MS = 10;
|
|
11
|
+
var delay = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
12
|
+
async function missing(error) {
|
|
13
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
14
|
+
}
|
|
15
|
+
function existing(error) {
|
|
16
|
+
return error instanceof Error && "code" in error && error.code === "EEXIST";
|
|
17
|
+
}
|
|
18
|
+
async function ownerState(lockPath) {
|
|
19
|
+
let owner;
|
|
20
|
+
try {
|
|
21
|
+
owner = JSON.parse(await readFile(lockPath, "utf8"));
|
|
22
|
+
} catch {
|
|
23
|
+
return "unknown";
|
|
24
|
+
}
|
|
25
|
+
if (typeof owner !== "object" || owner === null || !("pid" in owner) || typeof owner.pid !== "number" || !Number.isSafeInteger(owner.pid) || owner.pid <= 0) {
|
|
26
|
+
return "unknown";
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
process.kill(owner.pid, 0);
|
|
30
|
+
return "alive";
|
|
31
|
+
} catch (error) {
|
|
32
|
+
return error instanceof Error && "code" in error && error.code === "ESRCH" ? "gone" : "unknown";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function sweepTemporaries(path, staleLockMs) {
|
|
36
|
+
const prefix = `${basename(path)}.tmp.`;
|
|
37
|
+
const directory = dirname(path);
|
|
38
|
+
let entries;
|
|
39
|
+
try {
|
|
40
|
+
entries = await readdir(directory);
|
|
41
|
+
} catch {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
for (const entry of entries) {
|
|
45
|
+
if (!entry.startsWith(prefix))
|
|
46
|
+
continue;
|
|
47
|
+
const candidate = join(directory, entry);
|
|
48
|
+
const info = await stat(candidate).catch(() => null);
|
|
49
|
+
if (info && Date.now() - info.mtimeMs > staleLockMs) {
|
|
50
|
+
await unlink(candidate).catch(() => {
|
|
51
|
+
return;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async function reclaim(lockPath, observed, staleLockMs, abandonedAfterMs) {
|
|
57
|
+
const guardPath = `${lockPath}.reclaim`;
|
|
58
|
+
let guard;
|
|
59
|
+
try {
|
|
60
|
+
guard = await open(guardPath, "wx");
|
|
61
|
+
} catch (error) {
|
|
62
|
+
if (!existing(error))
|
|
63
|
+
throw error;
|
|
64
|
+
const orphan = await stat(guardPath).catch(() => null);
|
|
65
|
+
if (orphan && Date.now() - orphan.mtimeMs > staleLockMs) {
|
|
66
|
+
await unlink(guardPath).catch(() => {
|
|
67
|
+
return;
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const current = await stat(lockPath).catch(() => null);
|
|
74
|
+
if (!current || current.dev !== observed.dev || current.ino !== observed.ino || current.mtimeMs !== observed.mtimeMs) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
const owner = await ownerState(lockPath);
|
|
78
|
+
const age = Date.now() - current.mtimeMs;
|
|
79
|
+
if (owner !== "gone" && age < abandonedAfterMs)
|
|
80
|
+
return false;
|
|
81
|
+
await unlink(lockPath);
|
|
82
|
+
return true;
|
|
83
|
+
} finally {
|
|
84
|
+
await guard.close().catch(() => {
|
|
85
|
+
return;
|
|
86
|
+
});
|
|
87
|
+
await unlink(guardPath).catch(() => {
|
|
88
|
+
return;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function createFileStateStore(path, options) {
|
|
93
|
+
const lockPath = `${path}.lock`;
|
|
94
|
+
const lockTimeoutMs = options.lockTimeoutMs ?? 1e4;
|
|
95
|
+
const staleLockMs = options.staleLockMs ?? 3000;
|
|
96
|
+
const retryMs = options.retryMs ?? 10;
|
|
97
|
+
const heartbeatMs = Math.max(MIN_HEARTBEAT_MS, Math.floor(staleLockMs / 3));
|
|
98
|
+
const abandonedAfterMs = Math.max(staleLockMs * ABANDONED_LOCK_MULTIPLIER, heartbeatMs * ABANDONED_HEARTBEATS);
|
|
99
|
+
if (staleLockMs >= lockTimeoutMs) {
|
|
100
|
+
throw new Error(`[stitchkit] createFileStateStore: staleLockMs (${staleLockMs}) must be below lockTimeoutMs (${lockTimeoutMs}), or a crashed holder blocks every update until the timeout`);
|
|
101
|
+
}
|
|
102
|
+
let sweptTemporaries = false;
|
|
103
|
+
const parseFile = async () => {
|
|
104
|
+
let source;
|
|
105
|
+
try {
|
|
106
|
+
source = await readFile(path, "utf8");
|
|
107
|
+
} catch (error) {
|
|
108
|
+
if (await missing(error))
|
|
109
|
+
return null;
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
return options.schema.parse(JSON.parse(source));
|
|
114
|
+
} catch (error) {
|
|
115
|
+
await options.onCorrupt?.({ path, error });
|
|
116
|
+
if ((options.corrupt ?? "throw") === "empty")
|
|
117
|
+
return null;
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
const acquire = async () => {
|
|
122
|
+
await mkdir(dirname(path), { recursive: true });
|
|
123
|
+
if (!sweptTemporaries) {
|
|
124
|
+
sweptTemporaries = true;
|
|
125
|
+
await sweepTemporaries(path, staleLockMs);
|
|
126
|
+
}
|
|
127
|
+
const deadline = Date.now() + lockTimeoutMs;
|
|
128
|
+
while (true) {
|
|
129
|
+
try {
|
|
130
|
+
const handle = await open(lockPath, "wx");
|
|
131
|
+
const token = crypto.randomUUID();
|
|
132
|
+
try {
|
|
133
|
+
await handle.writeFile(JSON.stringify({ token, pid: process.pid, acquiredAt: Date.now() }));
|
|
134
|
+
await handle.sync();
|
|
135
|
+
} catch (error) {
|
|
136
|
+
await handle.close().catch(() => {
|
|
137
|
+
return;
|
|
138
|
+
});
|
|
139
|
+
await unlink(lockPath).catch(() => {
|
|
140
|
+
return;
|
|
141
|
+
});
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
refresh: async () => {
|
|
146
|
+
const now = new Date;
|
|
147
|
+
await handle.utimes(now, now);
|
|
148
|
+
},
|
|
149
|
+
release: async () => {
|
|
150
|
+
await handle.close();
|
|
151
|
+
try {
|
|
152
|
+
const owner = JSON.parse(await readFile(lockPath, "utf8"));
|
|
153
|
+
if (isRecord(owner) && owner.token === token)
|
|
154
|
+
await unlink(lockPath);
|
|
155
|
+
} catch (error) {
|
|
156
|
+
if (!await missing(error))
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
} catch (error) {
|
|
162
|
+
if (!existing(error))
|
|
163
|
+
throw error;
|
|
164
|
+
try {
|
|
165
|
+
const lock = await stat(lockPath);
|
|
166
|
+
if (Date.now() - lock.mtimeMs > staleLockMs) {
|
|
167
|
+
if (await reclaim(lockPath, lock, staleLockMs, abandonedAfterMs))
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
} catch (statError) {
|
|
171
|
+
if (await missing(statError))
|
|
172
|
+
continue;
|
|
173
|
+
throw statError;
|
|
174
|
+
}
|
|
175
|
+
if (Date.now() >= deadline) {
|
|
176
|
+
throw new Error(`[stitchkit] timed out acquiring state lock for ${path}`);
|
|
177
|
+
}
|
|
178
|
+
await delay(retryMs);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
const write = async (state) => {
|
|
183
|
+
const validated = options.schema.parse(state);
|
|
184
|
+
const temporary = `${path}.tmp.${process.pid}.${crypto.randomUUID()}`;
|
|
185
|
+
const handle = await open(temporary, "wx");
|
|
186
|
+
try {
|
|
187
|
+
try {
|
|
188
|
+
await handle.writeFile(`${JSON.stringify(validated)}
|
|
189
|
+
`);
|
|
190
|
+
await handle.sync();
|
|
191
|
+
} finally {
|
|
192
|
+
await handle.close();
|
|
193
|
+
}
|
|
194
|
+
await rename(temporary, path);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
await unlink(temporary).catch(() => {
|
|
197
|
+
return;
|
|
198
|
+
});
|
|
199
|
+
throw error;
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
return {
|
|
203
|
+
read: parseFile,
|
|
204
|
+
async update(transition) {
|
|
205
|
+
const lock = await acquire();
|
|
206
|
+
const heartbeat = setInterval(() => void lock.refresh().catch(() => {
|
|
207
|
+
return;
|
|
208
|
+
}), heartbeatMs);
|
|
209
|
+
try {
|
|
210
|
+
const next = await transition(await parseFile());
|
|
211
|
+
await write(next.state);
|
|
212
|
+
return next.result;
|
|
213
|
+
} finally {
|
|
214
|
+
clearInterval(heartbeat);
|
|
215
|
+
await lock.release();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export { createFileStateStore };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** The phases of a live voice conversation, one vocabulary for every side of it. */
|
|
2
|
+
export declare const LIVE_VOICE_PHASES: readonly ['opening', 'idle', 'hearing', 'thinking', 'speaking'];
|
|
3
|
+
export type LiveVoicePhase = (typeof LIVE_VOICE_PHASES)[number];
|
|
4
|
+
//# sourceMappingURL=phases.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"phases.d.ts","sourceRoot":"","sources":["../../src/voice/phases.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,eAAO,MAAM,iBAAiB,YAC5B,SAAS,EACT,MAAM,EACN,SAAS,EACT,UAAU,EACV,UAAU,CACF,CAAC;AACX,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare class SentenceCutter {
|
|
2
|
+
private said;
|
|
3
|
+
/** The sentences finished since the previous call; with `final`, the rest of the turn too. */
|
|
4
|
+
next(text: string, final: boolean): string[];
|
|
5
|
+
/** Start a new turn. */
|
|
6
|
+
reset(): void;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=sentences.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sentences.d.ts","sourceRoot":"","sources":["../../src/voice/sentences.ts"],"names":[],"mappings":"AAkBA,qBAAa,cAAc;IACzB,OAAO,CAAC,IAAI,CAAK;IAEjB,8FAA8F;IAC9F,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAmB3C;IAED,wBAAwB;IACxB,KAAK,IAAI,IAAI,CAEZ;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"speakable.d.ts","sourceRoot":"","sources":["../../src/voice/speakable.ts"],"names":[],"mappings":"AAiBA,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAsBtD"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export interface SpeechQueueOptions<Prepared> {
|
|
2
|
+
/** Synthesise one sentence. Aborted when the queue is cancelled. */
|
|
3
|
+
prepare: (text: string, signal: AbortSignal) => Promise<Prepared>;
|
|
4
|
+
/** Play one prepared sentence to its end. Aborted when the queue is cancelled. */
|
|
5
|
+
play: (prepared: Prepared, signal: AbortSignal) => Promise<void>;
|
|
6
|
+
/** Sentences prepared ahead of the one playing. Default 1; 0 prepares only after playing. */
|
|
7
|
+
lookahead?: number;
|
|
8
|
+
/** A sentence that failed to prepare or play is skipped; the queue goes on. */
|
|
9
|
+
onError?: (error: unknown, text: string) => void;
|
|
10
|
+
/** A prepared sentence that will never play — cancelled — to release what it holds. */
|
|
11
|
+
discard?: (prepared: Prepared) => void;
|
|
12
|
+
}
|
|
13
|
+
export declare class SpeechQueue<Prepared> {
|
|
14
|
+
private readonly options;
|
|
15
|
+
readonly done: Promise<void>;
|
|
16
|
+
private readonly lookahead;
|
|
17
|
+
private readonly pending;
|
|
18
|
+
private readonly ahead;
|
|
19
|
+
private current;
|
|
20
|
+
private playing;
|
|
21
|
+
private closed;
|
|
22
|
+
private cancelled;
|
|
23
|
+
private wake;
|
|
24
|
+
private stop;
|
|
25
|
+
private readonly stopped;
|
|
26
|
+
constructor(options: SpeechQueueOptions<Prepared>);
|
|
27
|
+
get isCancelled(): boolean;
|
|
28
|
+
get isSpeaking(): boolean;
|
|
29
|
+
/** Add one sentence. Blank text and anything after `close` or `cancel` is ignored. */
|
|
30
|
+
push(text: string): void;
|
|
31
|
+
/** The turn is over: say what is queued, then finish. */
|
|
32
|
+
close(): void;
|
|
33
|
+
/** Interrupted: abort the sentence playing and every synthesis, drop the rest. */
|
|
34
|
+
cancel(): void;
|
|
35
|
+
private notify;
|
|
36
|
+
/** Start synthesis up to the lookahead: the sentence playing or next, plus `lookahead`. */
|
|
37
|
+
private fill;
|
|
38
|
+
private run;
|
|
39
|
+
private report;
|
|
40
|
+
private release;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=speech-queue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"speech-queue.d.ts","sourceRoot":"","sources":["../../src/voice/speech-queue.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,kBAAkB,CAAC,QAAQ;IAC1C,oEAAoE;IACpE,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IAClE,kFAAkF;IAClF,IAAI,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,6FAA6F;IAC7F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+EAA+E;IAC/E,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,uFAAuF;IACvF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAAC;CACxC;AAqBD,qBAAa,WAAW,CAAC,QAAQ;IAenB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAdpC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgB;IACxC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAwB;IAC9C,OAAO,CAAC,OAAO,CAA+B;IAC9C,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,IAAI,CAA6B;IACzC,OAAO,CAAC,IAAI,CAAqC;IACjD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAErB;IAEH,YAA6B,OAAO,EAAE,kBAAkB,CAAC,QAAQ,CAAC,EAOjE;IAED,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED,IAAI,UAAU,IAAI,OAAO,CAExB;IAED,sFAAsF;IACtF,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAKvB;IAED,yDAAyD;IACzD,KAAK,IAAI,IAAI,CAGZ;IAED,kFAAkF;IAClF,MAAM,IAAI,IAAI,CAgBb;IAED,OAAO,CAAC,MAAM;IAId,2FAA2F;IAC3F,OAAO,CAAC,IAAI;YAaE,GAAG;IAmCjB,OAAO,CAAC,MAAM;IAQd,OAAO,CAAC,OAAO;CAOhB"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# stitchkit/application/directory-inbox — stitchkit docs slice
|
|
2
|
+
|
|
3
|
+
> Generated from the `docs/` tree by `bun run gen:llms`. The index of all slices is
|
|
4
|
+
> `llms.txt` in this package.
|
|
5
|
+
|
|
6
|
+
Contains: pointers only
|
|
7
|
+
|
|
8
|
+
Also read:
|
|
9
|
+
- see `llms/application.txt` for the Managed application kernel guide
|
package/llms/application.2.txt
CHANGED
|
@@ -299,6 +299,8 @@ cutovers are covered by the executable
|
|
|
299
299
|
| `NotificationOutboxItem` / `NotificationOutboxReceipt` / `NotificationOutboxState` / `NotificationSend` / `NotificationFailureClassification` / `DroppedNotification` / `EnqueueNotification` | _type_ | versioned queue records, stable send identity and delivery decisions |
|
|
300
300
|
| `notificationOutboxStateSchema` | function | strict persisted-state schema over an application payload schema |
|
|
301
301
|
| `notificationOutboxResource` / `NotificationOutboxResource` / `NotificationOutboxResourceConfig` | function / _type_ | thin managed-resource scheduler over an outbox handle |
|
|
302
|
+
| `createDirectoryInbox` (from `stitchkit/application/directory-inbox`) / `DirectoryInbox` / `DirectoryInboxConfig` / `DirectoryInboxDelivery` / `DirectoryInboxResource` | function / _type_ | managed resource delivering each `<name>.json` entry another program drops into a directory to the application at least once, after readiness; separate durable taken/done records, leases, bounded retry |
|
|
303
|
+
| `DirectoryInboxStateSchema` / `DirectoryInboxState` / `DirectoryInboxRejection` / `DirectoryInboxRejectionReasonSchema` / `DirectoryInboxRejectionReason` | schema / _type_ | the inbox's claims, receipts and rejections (`invalid`, `too-large`, `attempt-limit`) |
|
|
302
304
|
| `ApplicationHandle.restart` | method | replace one resource and everything that depends on it, leaving the rest of the graph running and the process epoch unchanged |
|
|
303
305
|
| `ApplicationRestartInputSchema` / `ApplicationRestartInput` | schema / _type_ | the resource to replace, by id, and optionally `gracePeriodMs` / `forceTimeoutMs` for this restart — the application's own shutdown budget otherwise |
|
|
304
306
|
| `ApplicationRestartResultSchema` / `ApplicationRestartResult` | schema / _type_ | the subtree that was actually taken down and brought back, in start order, with the outcome, the reason on anything but success, and how long it took |
|
package/llms/application.txt
CHANGED
|
@@ -905,7 +905,36 @@ deadline holds `stop()` — and so the resource's `force()` — for that one cal
|
|
|
905
905
|
so give the transport a timeout. Superseding a key that is being sent right now
|
|
906
906
|
does not recall it: the send completes, and only its receipt is not written.
|
|
907
907
|
|
|
908
|
-
|
|
908
|
+
`createDirectoryInbox` is the receiving side for another program on the same
|
|
909
|
+
host: it drops entries — a release transition, say — into a directory, and
|
|
910
|
+
the application takes each one at least once. It is a managed resource of its
|
|
911
|
+
own (`createDirectoryInbox({ id, directory, schema, handle })`, imported from
|
|
912
|
+
`stitchkit/application/directory-inbox` because it touches files; its types
|
|
913
|
+
and schemas are in `stitchkit/application`) and publishes a handle with
|
|
914
|
+
`flush()` and `state()`. The producer writes each entry
|
|
915
|
+
atomically as `<name>.json` — a temporary name, then a rename; names that start
|
|
916
|
+
with a dot are never read. Entries are delivered in name order, and only after
|
|
917
|
+
`activate`: none reaches the application before it is ready. `stopAdmission`
|
|
918
|
+
stops taking new ones, `drain` waits for the one in flight, and `force` aborts
|
|
919
|
+
the `signal` its handler was given.
|
|
920
|
+
|
|
921
|
+
Taken and done are separate durable records. Before `handle` runs, the entry
|
|
922
|
+
is claimed with a lease (`leaseMs`, default five minutes); after it returns,
|
|
923
|
+
a receipt replaces the claim and only then is the file removed. A process that
|
|
924
|
+
died mid-delivery leaves a claim, and the entry is taken again once the lease
|
|
925
|
+
runs out — at least once, not exactly once, so a handler that sends somewhere
|
|
926
|
+
should carry the entry's `key` as its idempotency key. A process that died
|
|
927
|
+
after the receipt leaves only the file, which the next pass removes without
|
|
928
|
+
delivering it again. A handler that throws is retried after a backoff from one
|
|
929
|
+
second doubling to five minutes; an entry that fails `schema`, is over
|
|
930
|
+
`maxEntryBytes` or was taken `maxAttempts` times (default 20 — the count
|
|
931
|
+
survives restarts, so an entry whose handling kills the process is not taken
|
|
932
|
+
forever) is moved to `rejected/` with its reason and reported to `onRejected`,
|
|
933
|
+
and the entries after it keep flowing. The state lives in
|
|
934
|
+
`<directory>/.inbox-state.json` under `createFileStateStore`'s inter-process
|
|
935
|
+
lock unless a `store` is supplied.
|
|
936
|
+
|
|
937
|
+
These primitives depend on the structural `StateStore`. On a server,
|
|
909
938
|
`createFileStateStore` supplies the shared Zod-validated JSON adapter with an
|
|
910
939
|
inter-process lock, unique temporary file, fsync and atomic rename. The lock
|
|
911
940
|
is a file with a heartbeat: the holder refreshes its mtime every third of
|
package/llms/stitchkit.txt
CHANGED
|
@@ -73,6 +73,7 @@ own, recorded as an ADR.
|
|
|
73
73
|
| `stitchkit/tracking` | browser **and** server | evolving | visitor-tracking mechanics — `createTrackingClient`, the tab-shared outbox, the page-leave beacon, attribution, the contract factory; no event vocabulary, no React |
|
|
74
74
|
| `stitchkit/tracking/server` | server (Bun or Node) | evolving | the decisions a tracking backend makes — dispositions, visit lease over an application-owned store, active intervals, presence; no database |
|
|
75
75
|
| `stitchkit/release` | browser **and** server | evolving | a page follows the release it was built for — `createReleaseMarker` on the server, `createReleaseWatcher` in the browser, the `X-Build-Id` header and a socket event between them |
|
|
76
|
+
| `stitchkit/voice` | browser **and** server | evolving | the mechanics of a live voice reply — `SentenceCutter`, `speakableText`, a `SpeechQueue` that synthesises ahead and stops on interruption, the conversation phases |
|
|
76
77
|
| `stitchkit/geo` | server (Bun or Node) | evolving | managed GeoIP reader generations, last-known-good reload and the optional MaxMind adapter |
|
|
77
78
|
| `stitchkit/oauth` | browser **and** server | evolving | provider-neutral Authorization Code + PKCE request and one-shot callback transaction mechanics |
|
|
78
79
|
| `stitchkit/google` | server (Bun or Node) | evolving | optional Google code exchange and verified OIDC identity adapter |
|
|
@@ -95,6 +96,7 @@ own, recorded as an ADR.
|
|
|
95
96
|
| `stitchkit/application/opentelemetry` | server | evolving | maps application snapshots onto an injected OpenTelemetry `Meter` |
|
|
96
97
|
| `stitchkit/application/schemas` | browser + server | evolving | the application's snapshot, health and shutdown schemas alone, without the kernel |
|
|
97
98
|
| `stitchkit/application/diagnostic-journal` | server | evolving | the local diagnostic journal — the one part of the kernel that spawns, locks and writes files |
|
|
99
|
+
| `stitchkit/application/directory-inbox` | server | evolving | the directory inbox — entries another program drops into a directory, delivered to the application at least once |
|
|
98
100
|
|
|
99
101
|
Rule of thumb: browser code imports `stitchkit` and `stitchkit/react`; server
|
|
100
102
|
code adds `stitchkit/server` (or `stitchkit/node` on Node) and opts into
|
package/llms/voice.txt
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# stitchkit/voice — stitchkit docs slice
|
|
2
|
+
|
|
3
|
+
> Generated from the `docs/` tree by `bun run gen:llms`. The index of all slices is
|
|
4
|
+
> `llms.txt` in this package.
|
|
5
|
+
|
|
6
|
+
Contains: Guide: Voice (docs/guide/voice.md) · API reference: `stitchkit/voice` (docs/api/reference.md)
|
|
7
|
+
|
|
8
|
+
==============================================================================
|
|
9
|
+
# Guide: Voice (docs/guide/voice.md)
|
|
10
|
+
==============================================================================
|
|
11
|
+
|
|
12
|
+
# Voice
|
|
13
|
+
|
|
14
|
+
A live voice conversation with an agent repeats the same mechanics in every application
|
|
15
|
+
that has one: the reply is cut into sentences while the model is still writing it, each
|
|
16
|
+
sentence is stripped down to what can be said aloud, and a queue speaks them in order — and
|
|
17
|
+
stops at once when the listener interrupts. `stitchkit/voice` is those mechanics, the same in
|
|
18
|
+
the browser and on Bun or Node, with no dependencies. It is **evolving**.
|
|
19
|
+
|
|
20
|
+
Speech recognition, synthesis providers, the microphone and the player stay with the
|
|
21
|
+
application: the queue takes them as two functions.
|
|
22
|
+
|
|
23
|
+
## Sentences while the reply streams
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { SentenceCutter, SpeechQueue, speakableText } from 'stitchkit/voice'
|
|
27
|
+
|
|
28
|
+
const cutter = new SentenceCutter()
|
|
29
|
+
|
|
30
|
+
for await (const text of reply) { // the whole reply so far, on every chunk
|
|
31
|
+
for (const sentence of cutter.next(text, false)) say(sentence)
|
|
32
|
+
}
|
|
33
|
+
for (const sentence of cutter.next(finalText, true)) say(sentence) // the rest of the turn
|
|
34
|
+
cutter.reset() // the next turn starts from zero
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
A sentence ends at `. ! ? …` (with any closing quote or bracket) followed by whitespace and a
|
|
38
|
+
capital letter, a digit or an opening mark, or at a blank line between paragraphs. `3.5`,
|
|
39
|
+
`т. е. так` and `e.g. this` are not cut; the end of the text is not the end of a sentence
|
|
40
|
+
until `final`; nothing is cut inside a fenced code block, even one that is still open.
|
|
41
|
+
|
|
42
|
+
## What is said
|
|
43
|
+
|
|
44
|
+
`speakableText(markdown)` keeps what a reader sees and drops what cannot be said: headings,
|
|
45
|
+
emphasis and list markers lose their delimiters, a link keeps its text and loses its
|
|
46
|
+
address, and images, bare addresses, fenced code blocks and tables are dropped whole. It does
|
|
47
|
+
not summarise. A sentence that was only code becomes `''`, and the queue ignores blank text.
|
|
48
|
+
|
|
49
|
+
## Speaking in order, synthesising ahead
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
const queue = new SpeechQueue<Blob>({
|
|
53
|
+
prepare: (text, signal) => synthesise(text, { signal }), // the whole clip
|
|
54
|
+
play: (clip, signal) => player.play(clip, { signal }), // resolves when it has played
|
|
55
|
+
lookahead: 1, // the default
|
|
56
|
+
onError: (error, text) => log.warn('sentence skipped', { error, text }),
|
|
57
|
+
discard: (clip) => release(clip), // prepared, never played
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const say = (sentence: string) => queue.push(speakableText(sentence))
|
|
61
|
+
|
|
62
|
+
// the turn is over: say what is queued, then `done` settles
|
|
63
|
+
queue.close()
|
|
64
|
+
// the listener interrupted: the playing sentence and every synthesis are aborted
|
|
65
|
+
queue.cancel()
|
|
66
|
+
await queue.done
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
While one sentence plays, up to `lookahead` more are already synthesising, so a synthesiser
|
|
70
|
+
that returns the whole clip does not leave a gap the length of its synthesis between two
|
|
71
|
+
sentences. `lookahead: 0` synthesises each sentence only after the previous one played.
|
|
72
|
+
|
|
73
|
+
A sentence that fails to synthesise or to play is reported to `onError` and skipped; the
|
|
74
|
+
queue goes on. `cancel()` aborts the signal of the sentence playing and of every synthesis in
|
|
75
|
+
flight, drops the rest, calls `discard` for every clip that was prepared and will not play,
|
|
76
|
+
and settles `done` without waiting for a `play` that ignores its signal. `isSpeaking` and
|
|
77
|
+
`isCancelled` say where the queue is.
|
|
78
|
+
|
|
79
|
+
## Phases
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import { LIVE_VOICE_PHASES, type LiveVoicePhase } from 'stitchkit/voice'
|
|
83
|
+
// 'opening' | 'idle' | 'hearing' | 'thinking' | 'speaking'
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
One vocabulary for every side of the conversation — the server that runs the agent and the
|
|
87
|
+
page that shows the microphone — so a phase sent over the wire means the same on both ends.
|
|
88
|
+
|
|
89
|
+
==============================================================================
|
|
90
|
+
# API reference: `stitchkit/voice` (docs/api/reference.md)
|
|
91
|
+
==============================================================================
|
|
92
|
+
|
|
93
|
+
## `stitchkit/voice`
|
|
94
|
+
|
|
95
|
+
Isomorphic, dependency-free mechanics of a live voice conversation. Recognition, synthesis
|
|
96
|
+
providers, the microphone and the player remain application-owned. See the
|
|
97
|
+
[voice guide](../guide/voice.md).
|
|
98
|
+
|
|
99
|
+
| Export | Kind | Summary |
|
|
100
|
+
|--------|------|---------|
|
|
101
|
+
| `SentenceCutter` | class | `next(text, final)` returns the sentences finished since the previous call of the reply streamed so far — never inside a fenced code block, never at `3.5` or `т. е.`; `reset()` starts a new turn |
|
|
102
|
+
| `speakableText` | function | the visible text of Markdown without delimiters, link and image addresses, bare addresses, fenced code blocks or tables |
|
|
103
|
+
| `SpeechQueue` / `SpeechQueueOptions` | class / _type_ | speak sentences in order with `lookahead` (default 1) synthesised while one plays; `close()` finishes the queue, `cancel()` aborts the playing sentence and every synthesis; a failed sentence is reported to `onError` and skipped |
|
|
104
|
+
| `LIVE_VOICE_PHASES` / `LiveVoicePhase` | constant / _type_ | `opening`, `idle`, `hearing`, `thinking`, `speaking` |
|
|
105
|
+
|
|
106
|
+
---
|
package/llms-full.txt
CHANGED
|
@@ -64,6 +64,7 @@ own, recorded as an ADR.
|
|
|
64
64
|
| `stitchkit/tracking` | browser **and** server | evolving | visitor-tracking mechanics — `createTrackingClient`, the tab-shared outbox, the page-leave beacon, attribution, the contract factory; no event vocabulary, no React |
|
|
65
65
|
| `stitchkit/tracking/server` | server (Bun or Node) | evolving | the decisions a tracking backend makes — dispositions, visit lease over an application-owned store, active intervals, presence; no database |
|
|
66
66
|
| `stitchkit/release` | browser **and** server | evolving | a page follows the release it was built for — `createReleaseMarker` on the server, `createReleaseWatcher` in the browser, the `X-Build-Id` header and a socket event between them |
|
|
67
|
+
| `stitchkit/voice` | browser **and** server | evolving | the mechanics of a live voice reply — `SentenceCutter`, `speakableText`, a `SpeechQueue` that synthesises ahead and stops on interruption, the conversation phases |
|
|
67
68
|
| `stitchkit/geo` | server (Bun or Node) | evolving | managed GeoIP reader generations, last-known-good reload and the optional MaxMind adapter |
|
|
68
69
|
| `stitchkit/oauth` | browser **and** server | evolving | provider-neutral Authorization Code + PKCE request and one-shot callback transaction mechanics |
|
|
69
70
|
| `stitchkit/google` | server (Bun or Node) | evolving | optional Google code exchange and verified OIDC identity adapter |
|
|
@@ -86,6 +87,7 @@ own, recorded as an ADR.
|
|
|
86
87
|
| `stitchkit/application/opentelemetry` | server | evolving | maps application snapshots onto an injected OpenTelemetry `Meter` |
|
|
87
88
|
| `stitchkit/application/schemas` | browser + server | evolving | the application's snapshot, health and shutdown schemas alone, without the kernel |
|
|
88
89
|
| `stitchkit/application/diagnostic-journal` | server | evolving | the local diagnostic journal — the one part of the kernel that spawns, locks and writes files |
|
|
90
|
+
| `stitchkit/application/directory-inbox` | server | evolving | the directory inbox — entries another program drops into a directory, delivered to the application at least once |
|
|
89
91
|
|
|
90
92
|
Rule of thumb: browser code imports `stitchkit` and `stitchkit/react`; server
|
|
91
93
|
code adds `stitchkit/server` (or `stitchkit/node` on Node) and opts into
|
|
@@ -7151,7 +7153,36 @@ deadline holds `stop()` — and so the resource's `force()` — for that one cal
|
|
|
7151
7153
|
so give the transport a timeout. Superseding a key that is being sent right now
|
|
7152
7154
|
does not recall it: the send completes, and only its receipt is not written.
|
|
7153
7155
|
|
|
7154
|
-
|
|
7156
|
+
`createDirectoryInbox` is the receiving side for another program on the same
|
|
7157
|
+
host: it drops entries — a release transition, say — into a directory, and
|
|
7158
|
+
the application takes each one at least once. It is a managed resource of its
|
|
7159
|
+
own (`createDirectoryInbox({ id, directory, schema, handle })`, imported from
|
|
7160
|
+
`stitchkit/application/directory-inbox` because it touches files; its types
|
|
7161
|
+
and schemas are in `stitchkit/application`) and publishes a handle with
|
|
7162
|
+
`flush()` and `state()`. The producer writes each entry
|
|
7163
|
+
atomically as `<name>.json` — a temporary name, then a rename; names that start
|
|
7164
|
+
with a dot are never read. Entries are delivered in name order, and only after
|
|
7165
|
+
`activate`: none reaches the application before it is ready. `stopAdmission`
|
|
7166
|
+
stops taking new ones, `drain` waits for the one in flight, and `force` aborts
|
|
7167
|
+
the `signal` its handler was given.
|
|
7168
|
+
|
|
7169
|
+
Taken and done are separate durable records. Before `handle` runs, the entry
|
|
7170
|
+
is claimed with a lease (`leaseMs`, default five minutes); after it returns,
|
|
7171
|
+
a receipt replaces the claim and only then is the file removed. A process that
|
|
7172
|
+
died mid-delivery leaves a claim, and the entry is taken again once the lease
|
|
7173
|
+
runs out — at least once, not exactly once, so a handler that sends somewhere
|
|
7174
|
+
should carry the entry's `key` as its idempotency key. A process that died
|
|
7175
|
+
after the receipt leaves only the file, which the next pass removes without
|
|
7176
|
+
delivering it again. A handler that throws is retried after a backoff from one
|
|
7177
|
+
second doubling to five minutes; an entry that fails `schema`, is over
|
|
7178
|
+
`maxEntryBytes` or was taken `maxAttempts` times (default 20 — the count
|
|
7179
|
+
survives restarts, so an entry whose handling kills the process is not taken
|
|
7180
|
+
forever) is moved to `rejected/` with its reason and reported to `onRejected`,
|
|
7181
|
+
and the entries after it keep flowing. The state lives in
|
|
7182
|
+
`<directory>/.inbox-state.json` under `createFileStateStore`'s inter-process
|
|
7183
|
+
lock unless a `store` is supplied.
|
|
7184
|
+
|
|
7185
|
+
These primitives depend on the structural `StateStore`. On a server,
|
|
7155
7186
|
`createFileStateStore` supplies the shared Zod-validated JSON adapter with an
|
|
7156
7187
|
inter-process lock, unique temporary file, fsync and atomic rename. The lock
|
|
7157
7188
|
is a file with a heartbeat: the holder refreshes its mtime every third of
|
|
@@ -12603,6 +12634,88 @@ session query with `cache`; the generic starter does not invent a `getSession`
|
|
|
12603
12634
|
endpoint or cookie vocabulary.
|
|
12604
12635
|
|
|
12605
12636
|
|
|
12637
|
+
==============================================================================
|
|
12638
|
+
# Guide: Voice (docs/guide/voice.md)
|
|
12639
|
+
==============================================================================
|
|
12640
|
+
|
|
12641
|
+
# Voice
|
|
12642
|
+
|
|
12643
|
+
A live voice conversation with an agent repeats the same mechanics in every application
|
|
12644
|
+
that has one: the reply is cut into sentences while the model is still writing it, each
|
|
12645
|
+
sentence is stripped down to what can be said aloud, and a queue speaks them in order — and
|
|
12646
|
+
stops at once when the listener interrupts. `stitchkit/voice` is those mechanics, the same in
|
|
12647
|
+
the browser and on Bun or Node, with no dependencies. It is **evolving**.
|
|
12648
|
+
|
|
12649
|
+
Speech recognition, synthesis providers, the microphone and the player stay with the
|
|
12650
|
+
application: the queue takes them as two functions.
|
|
12651
|
+
|
|
12652
|
+
## Sentences while the reply streams
|
|
12653
|
+
|
|
12654
|
+
```ts
|
|
12655
|
+
import { SentenceCutter, SpeechQueue, speakableText } from 'stitchkit/voice'
|
|
12656
|
+
|
|
12657
|
+
const cutter = new SentenceCutter()
|
|
12658
|
+
|
|
12659
|
+
for await (const text of reply) { // the whole reply so far, on every chunk
|
|
12660
|
+
for (const sentence of cutter.next(text, false)) say(sentence)
|
|
12661
|
+
}
|
|
12662
|
+
for (const sentence of cutter.next(finalText, true)) say(sentence) // the rest of the turn
|
|
12663
|
+
cutter.reset() // the next turn starts from zero
|
|
12664
|
+
```
|
|
12665
|
+
|
|
12666
|
+
A sentence ends at `. ! ? …` (with any closing quote or bracket) followed by whitespace and a
|
|
12667
|
+
capital letter, a digit or an opening mark, or at a blank line between paragraphs. `3.5`,
|
|
12668
|
+
`т. е. так` and `e.g. this` are not cut; the end of the text is not the end of a sentence
|
|
12669
|
+
until `final`; nothing is cut inside a fenced code block, even one that is still open.
|
|
12670
|
+
|
|
12671
|
+
## What is said
|
|
12672
|
+
|
|
12673
|
+
`speakableText(markdown)` keeps what a reader sees and drops what cannot be said: headings,
|
|
12674
|
+
emphasis and list markers lose their delimiters, a link keeps its text and loses its
|
|
12675
|
+
address, and images, bare addresses, fenced code blocks and tables are dropped whole. It does
|
|
12676
|
+
not summarise. A sentence that was only code becomes `''`, and the queue ignores blank text.
|
|
12677
|
+
|
|
12678
|
+
## Speaking in order, synthesising ahead
|
|
12679
|
+
|
|
12680
|
+
```ts
|
|
12681
|
+
const queue = new SpeechQueue<Blob>({
|
|
12682
|
+
prepare: (text, signal) => synthesise(text, { signal }), // the whole clip
|
|
12683
|
+
play: (clip, signal) => player.play(clip, { signal }), // resolves when it has played
|
|
12684
|
+
lookahead: 1, // the default
|
|
12685
|
+
onError: (error, text) => log.warn('sentence skipped', { error, text }),
|
|
12686
|
+
discard: (clip) => release(clip), // prepared, never played
|
|
12687
|
+
})
|
|
12688
|
+
|
|
12689
|
+
const say = (sentence: string) => queue.push(speakableText(sentence))
|
|
12690
|
+
|
|
12691
|
+
// the turn is over: say what is queued, then `done` settles
|
|
12692
|
+
queue.close()
|
|
12693
|
+
// the listener interrupted: the playing sentence and every synthesis are aborted
|
|
12694
|
+
queue.cancel()
|
|
12695
|
+
await queue.done
|
|
12696
|
+
```
|
|
12697
|
+
|
|
12698
|
+
While one sentence plays, up to `lookahead` more are already synthesising, so a synthesiser
|
|
12699
|
+
that returns the whole clip does not leave a gap the length of its synthesis between two
|
|
12700
|
+
sentences. `lookahead: 0` synthesises each sentence only after the previous one played.
|
|
12701
|
+
|
|
12702
|
+
A sentence that fails to synthesise or to play is reported to `onError` and skipped; the
|
|
12703
|
+
queue goes on. `cancel()` aborts the signal of the sentence playing and of every synthesis in
|
|
12704
|
+
flight, drops the rest, calls `discard` for every clip that was prepared and will not play,
|
|
12705
|
+
and settles `done` without waiting for a `play` that ignores its signal. `isSpeaking` and
|
|
12706
|
+
`isCancelled` say where the queue is.
|
|
12707
|
+
|
|
12708
|
+
## Phases
|
|
12709
|
+
|
|
12710
|
+
```ts
|
|
12711
|
+
import { LIVE_VOICE_PHASES, type LiveVoicePhase } from 'stitchkit/voice'
|
|
12712
|
+
// 'opening' | 'idle' | 'hearing' | 'thinking' | 'speaking'
|
|
12713
|
+
```
|
|
12714
|
+
|
|
12715
|
+
One vocabulary for every side of the conversation — the server that runs the agent and the
|
|
12716
|
+
page that shows the microphone — so a phase sent over the wire means the same on both ends.
|
|
12717
|
+
|
|
12718
|
+
|
|
12606
12719
|
==============================================================================
|
|
12607
12720
|
# Guide: GeoIP (docs/guide/geo.md)
|
|
12608
12721
|
==============================================================================
|
|
@@ -16978,6 +17091,8 @@ cutovers are covered by the executable
|
|
|
16978
17091
|
| `NotificationOutboxItem` / `NotificationOutboxReceipt` / `NotificationOutboxState` / `NotificationSend` / `NotificationFailureClassification` / `DroppedNotification` / `EnqueueNotification` | _type_ | versioned queue records, stable send identity and delivery decisions |
|
|
16979
17092
|
| `notificationOutboxStateSchema` | function | strict persisted-state schema over an application payload schema |
|
|
16980
17093
|
| `notificationOutboxResource` / `NotificationOutboxResource` / `NotificationOutboxResourceConfig` | function / _type_ | thin managed-resource scheduler over an outbox handle |
|
|
17094
|
+
| `createDirectoryInbox` (from `stitchkit/application/directory-inbox`) / `DirectoryInbox` / `DirectoryInboxConfig` / `DirectoryInboxDelivery` / `DirectoryInboxResource` | function / _type_ | managed resource delivering each `<name>.json` entry another program drops into a directory to the application at least once, after readiness; separate durable taken/done records, leases, bounded retry |
|
|
17095
|
+
| `DirectoryInboxStateSchema` / `DirectoryInboxState` / `DirectoryInboxRejection` / `DirectoryInboxRejectionReasonSchema` / `DirectoryInboxRejectionReason` | schema / _type_ | the inbox's claims, receipts and rejections (`invalid`, `too-large`, `attempt-limit`) |
|
|
16981
17096
|
| `ApplicationHandle.restart` | method | replace one resource and everything that depends on it, leaving the rest of the graph running and the process epoch unchanged |
|
|
16982
17097
|
| `ApplicationRestartInputSchema` / `ApplicationRestartInput` | schema / _type_ | the resource to replace, by id, and optionally `gracePeriodMs` / `forceTimeoutMs` for this restart — the application's own shutdown budget otherwise |
|
|
16983
17098
|
| `ApplicationRestartResultSchema` / `ApplicationRestartResult` | schema / _type_ | the subtree that was actually taken down and brought back, in start order, with the outcome, the reason on anything but success, and how long it took |
|
|
@@ -18268,6 +18383,21 @@ signal stay with the application. → ADR 0167 — [guide](../guide/release.md)
|
|
|
18268
18383
|
|
|
18269
18384
|
---
|
|
18270
18385
|
|
|
18386
|
+
## `stitchkit/voice`
|
|
18387
|
+
|
|
18388
|
+
Isomorphic, dependency-free mechanics of a live voice conversation. Recognition, synthesis
|
|
18389
|
+
providers, the microphone and the player remain application-owned. See the
|
|
18390
|
+
[voice guide](../guide/voice.md).
|
|
18391
|
+
|
|
18392
|
+
| Export | Kind | Summary |
|
|
18393
|
+
|--------|------|---------|
|
|
18394
|
+
| `SentenceCutter` | class | `next(text, final)` returns the sentences finished since the previous call of the reply streamed so far — never inside a fenced code block, never at `3.5` or `т. е.`; `reset()` starts a new turn |
|
|
18395
|
+
| `speakableText` | function | the visible text of Markdown without delimiters, link and image addresses, bare addresses, fenced code blocks or tables |
|
|
18396
|
+
| `SpeechQueue` / `SpeechQueueOptions` | class / _type_ | speak sentences in order with `lookahead` (default 1) synthesised while one plays; `close()` finishes the queue, `cancel()` aborts the playing sentence and every synthesis; a failed sentence is reported to `onError` and skipped |
|
|
18397
|
+
| `LIVE_VOICE_PHASES` / `LiveVoicePhase` | constant / _type_ | `opening`, `idle`, `hearing`, `thinking`, `speaking` |
|
|
18398
|
+
|
|
18399
|
+
---
|
|
18400
|
+
|
|
18271
18401
|
## `stitchkit/geo`
|
|
18272
18402
|
|
|
18273
18403
|
Server-only managed GeoIP generations. Applications own database acquisition,
|