@osolmaz/pi-workflows 0.13.2 → 0.13.3

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 (130) hide show
  1. package/README.md +5 -3
  2. package/dist/builtins/autodoc.workflow.d.ts +5 -5
  3. package/dist/builtins/autoimplement.workflow.d.ts +65 -61
  4. package/dist/builtins/autoimplement.workflow.js +47 -8
  5. package/dist/builtins/autoimplement.workflow.js.map +1 -1
  6. package/dist/builtins/autoplan.workflow.d.ts +5 -5
  7. package/dist/builtins/catalog.js +1 -1
  8. package/dist/builtins/change-verification.workflow.d.ts +1 -1
  9. package/dist/builtins/plain-summary.workflow.d.ts +1 -1
  10. package/dist/builtins/plan-approval.workflow.d.ts +1 -1
  11. package/dist/builtins/plan-change.workflow.d.ts +23 -23
  12. package/dist/builtins/sanity-check.workflow.d.ts +3 -3
  13. package/dist/builtins/workspace-preparation.workflow.d.ts +1 -1
  14. package/dist/controllers/index.d.ts +2 -2
  15. package/dist/controllers/index.js.map +1 -1
  16. package/dist/controllers/sqlite.js +85 -23
  17. package/dist/controllers/sqlite.js.map +1 -1
  18. package/dist/controllers/types.d.ts +34 -0
  19. package/dist/controllers/workflow-engine-scheduler.d.ts +5 -1
  20. package/dist/controllers/workflow-engine-scheduler.js +83 -1
  21. package/dist/controllers/workflow-engine-scheduler.js.map +1 -1
  22. package/dist/controllers/workflows.d.ts +8 -1
  23. package/dist/controllers/workflows.js +36 -0
  24. package/dist/controllers/workflows.js.map +1 -1
  25. package/dist/extension/controller-host.d.ts +2 -1
  26. package/dist/extension/controller-host.js +1 -1
  27. package/dist/extension/controller-host.js.map +1 -1
  28. package/dist/extension/follow-up-coordinator.d.ts +27 -0
  29. package/dist/extension/follow-up-coordinator.js +131 -0
  30. package/dist/extension/follow-up-coordinator.js.map +1 -0
  31. package/dist/extension/index.d.ts +9 -0
  32. package/dist/extension/index.js +460 -75
  33. package/dist/extension/index.js.map +1 -1
  34. package/dist/extension/session-events.d.ts +2 -3
  35. package/dist/extension/session-events.js +11 -10
  36. package/dist/extension/session-events.js.map +1 -1
  37. package/dist/extension/widget.js +9 -0
  38. package/dist/extension/widget.js.map +1 -1
  39. package/dist/state/database.d.ts +2 -1
  40. package/dist/state/database.js +11 -13
  41. package/dist/state/database.js.map +1 -1
  42. package/dist/state/json.js +6 -1
  43. package/dist/state/json.js.map +1 -1
  44. package/dist/state/mutation.d.ts +1 -1
  45. package/dist/state/mutation.js.map +1 -1
  46. package/dist/state/prune.d.ts +18 -0
  47. package/dist/state/prune.js +373 -0
  48. package/dist/state/prune.js.map +1 -0
  49. package/dist/state/schema.d.ts +1 -1
  50. package/dist/state/schema.js +116 -18
  51. package/dist/state/schema.js.map +1 -1
  52. package/dist/viewer/cli.d.ts +3 -1
  53. package/dist/viewer/cli.js +55 -4
  54. package/dist/viewer/cli.js.map +1 -1
  55. package/dist/viewer/render.js +19 -1
  56. package/dist/viewer/render.js.map +1 -1
  57. package/dist/workflows/composition.d.ts +2 -0
  58. package/dist/workflows/composition.js +15 -0
  59. package/dist/workflows/composition.js.map +1 -1
  60. package/dist/workflows/definition.d.ts +6 -3
  61. package/dist/workflows/definition.js +3 -0
  62. package/dist/workflows/definition.js.map +1 -1
  63. package/dist/workflows/engine.d.ts +6 -1
  64. package/dist/workflows/engine.js +301 -35
  65. package/dist/workflows/engine.js.map +1 -1
  66. package/dist/workflows/human-decision.d.ts +1 -0
  67. package/dist/workflows/human-decision.js +25 -9
  68. package/dist/workflows/human-decision.js.map +1 -1
  69. package/dist/workflows/index.d.ts +3 -1
  70. package/dist/workflows/index.js +2 -0
  71. package/dist/workflows/index.js.map +1 -1
  72. package/dist/workflows/json-patch.d.ts +47 -0
  73. package/dist/workflows/json-patch.js +298 -0
  74. package/dist/workflows/json-patch.js.map +1 -0
  75. package/dist/workflows/schema.js +29 -1
  76. package/dist/workflows/schema.js.map +1 -1
  77. package/dist/workflows/settings.d.ts +128 -0
  78. package/dist/workflows/settings.js +199 -0
  79. package/dist/workflows/settings.js.map +1 -0
  80. package/dist/workflows/store.d.ts +75 -2
  81. package/dist/workflows/store.js +1349 -89
  82. package/dist/workflows/store.js.map +1 -1
  83. package/dist/workflows/tool-input.d.ts +22 -0
  84. package/dist/workflows/tool-input.js +43 -0
  85. package/dist/workflows/tool-input.js.map +1 -1
  86. package/dist/workflows/types.d.ts +40 -5
  87. package/docs/2026-08-25-workflow-follow-ups.md +132 -0
  88. package/docs/2026-08-25-workflow-settings.md +169 -0
  89. package/docs/DEFERRED_TURNS.md +6 -0
  90. package/docs/DESIGN_PHILOSOPHY.md +2 -0
  91. package/docs/SQLITE_STATE.md +31 -18
  92. package/docs/WORKFLOW_COMPOSITION.md +15 -0
  93. package/docs/WORKFLOW_STEP_MESSAGES.md +4 -2
  94. package/docs/plans/2026-08-25-live-workflow-settings-plan.md +532 -0
  95. package/docs/plans/2026-08-25-workflow-run-storage-plan.md +67 -0
  96. package/docs/tui-viewer.md +7 -6
  97. package/docs/workflows.md +22 -1
  98. package/examples/workflows/live-settings.workflow.ts +93 -0
  99. package/herdr-plugin.toml +1 -1
  100. package/package.json +1 -1
  101. package/src/builtins/autoimplement.workflow.ts +56 -8
  102. package/src/builtins/catalog.ts +1 -1
  103. package/src/controllers/index.ts +6 -0
  104. package/src/controllers/sqlite.ts +129 -35
  105. package/src/controllers/types.ts +40 -0
  106. package/src/controllers/workflow-engine-scheduler.ts +103 -1
  107. package/src/controllers/workflows.ts +68 -0
  108. package/src/extension/controller-host.ts +6 -1
  109. package/src/extension/follow-up-coordinator.ts +151 -0
  110. package/src/extension/index.ts +538 -88
  111. package/src/extension/session-events.ts +15 -13
  112. package/src/extension/widget.ts +8 -0
  113. package/src/state/database.ts +12 -12
  114. package/src/state/json.ts +6 -1
  115. package/src/state/mutation.ts +4 -1
  116. package/src/state/prune.ts +502 -0
  117. package/src/state/schema.ts +116 -18
  118. package/src/viewer/cli.ts +52 -5
  119. package/src/viewer/render.ts +43 -1
  120. package/src/workflows/composition.ts +19 -0
  121. package/src/workflows/definition.ts +19 -5
  122. package/src/workflows/engine.ts +363 -35
  123. package/src/workflows/human-decision.ts +41 -11
  124. package/src/workflows/index.ts +43 -0
  125. package/src/workflows/json-patch.ts +375 -0
  126. package/src/workflows/schema.ts +31 -1
  127. package/src/workflows/settings.ts +430 -0
  128. package/src/workflows/store.ts +1977 -184
  129. package/src/workflows/tool-input.ts +58 -0
  130. package/src/workflows/types.ts +43 -4
@@ -0,0 +1,430 @@
1
+ import { canonicalJson, type JsonValue } from "../state/json.js";
2
+ import type { ActorType, MutationActor } from "../state/mutation.js";
3
+ import {
4
+ MAX_WORKFLOW_SETTINGS_BYTES,
5
+ applyJsonPatchOperation,
6
+ cloneJson,
7
+ jsonPointerTarget,
8
+ parseJsonPointer,
9
+ validateJsonPatch,
10
+ type JsonPatch,
11
+ type JsonPatchOperation,
12
+ } from "./json-patch.js";
13
+ import type { ComputeNodeDefinition, MaybePromise, WorkflowValueParser } from "./types.js";
14
+
15
+ export type WorkflowSettingsPermission = "read" | "add" | "remove" | "replace";
16
+
17
+ export type WorkflowSettingsPathPermissions = Partial<
18
+ Record<WorkflowSettingsPermission, readonly ActorType[]>
19
+ >;
20
+
21
+ export type WorkflowSettingsPathRule = {
22
+ path: string;
23
+ permissions: WorkflowSettingsPathPermissions;
24
+ };
25
+
26
+ export type WorkflowSettingsChangeContext<TSettings> = {
27
+ before: TSettings;
28
+ after: TSettings;
29
+ actor: MutationActor;
30
+ source: string;
31
+ patch: JsonPatch;
32
+ };
33
+
34
+ export type WorkflowSettingsDefinition<TSettings = JsonValue, TInput = unknown> = {
35
+ initial: TSettings | ((input: TInput) => MaybePromise<TSettings>);
36
+ parse: WorkflowValueParser<TSettings>;
37
+ paths: readonly WorkflowSettingsPathRule[];
38
+ description?: string;
39
+ validateChange?: (context: WorkflowSettingsChangeContext<TSettings>) => MaybePromise<void>;
40
+ };
41
+
42
+ export type WorkflowSettingsOptions<TSettings, TInput = unknown> = {
43
+ initial: TSettings | ((input: TInput) => MaybePromise<TSettings>);
44
+ parse: WorkflowValueParser<TSettings>;
45
+ paths: readonly WorkflowSettingsPathRule[];
46
+ description?: string;
47
+ validateChange?: (context: WorkflowSettingsChangeContext<TSettings>) => MaybePromise<void>;
48
+ };
49
+
50
+ export type AppliedWorkflowSettings<TSettings> = {
51
+ patch: JsonPatch;
52
+ settings: TSettings;
53
+ json: JsonValue;
54
+ };
55
+
56
+ export type InitialWorkflowSettingsScope = {
57
+ mountPath: string;
58
+ invocation: number;
59
+ settings: JsonValue;
60
+ };
61
+
62
+ export type WorkflowSettingsScopeRecord = {
63
+ scopeId: string;
64
+ originRunId: string;
65
+ activeRunId: string;
66
+ mountPath: string;
67
+ invocation: number;
68
+ changeNumber: number;
69
+ settings: JsonValue;
70
+ settingsHash: string;
71
+ createdAt: string;
72
+ updatedAt: string;
73
+ };
74
+
75
+ export type WorkflowSettingsChangeRequest = {
76
+ runId: string;
77
+ scopeId: string;
78
+ requestId: string;
79
+ expectedChangeNumber?: number;
80
+ actor: MutationActor;
81
+ source: string;
82
+ patch: unknown;
83
+ };
84
+
85
+ export type WorkflowSettingsChangeRecord = {
86
+ changeId: string;
87
+ scopeId: string;
88
+ requestId: string;
89
+ changeNumber: number;
90
+ actor: MutationActor;
91
+ source: string;
92
+ patch: JsonPatch;
93
+ beforeHash: string;
94
+ afterHash: string;
95
+ acceptedAt: string;
96
+ };
97
+
98
+ export type WorkflowSettingsChangeResult = {
99
+ scope: WorkflowSettingsScopeRecord;
100
+ change: WorkflowSettingsChangeRecord;
101
+ adopted: boolean;
102
+ };
103
+
104
+ export type WorkflowFollowUpState =
105
+ | "queued"
106
+ | "pending_presentation"
107
+ | "ready"
108
+ | "sent"
109
+ | "removed"
110
+ | "cancelled";
111
+
112
+ export type WorkflowPresentationState =
113
+ | "none"
114
+ | "not-needed"
115
+ | "pending"
116
+ | "settled"
117
+ | "unavailable";
118
+
119
+ export type WorkflowFollowUpRecord = {
120
+ followUpId: string;
121
+ runId: string;
122
+ requestId: string;
123
+ order: number;
124
+ targetSessionId: string;
125
+ actor: MutationActor;
126
+ source: string;
127
+ prompt: string;
128
+ state: WorkflowFollowUpState;
129
+ sessionEntryId?: string;
130
+ reason?: string;
131
+ createdAt: string;
132
+ updatedAt: string;
133
+ sentAt?: string;
134
+ };
135
+
136
+ export type WorkflowFollowUpQueueRecord = {
137
+ runId: string;
138
+ originSessionId?: string;
139
+ presentationState: WorkflowPresentationState;
140
+ presentationEntryId?: string;
141
+ presentationAssistantEntryId?: string;
142
+ presentationReason?: string;
143
+ followUps: WorkflowFollowUpRecord[];
144
+ };
145
+
146
+ export type WorkflowQueueFollowUpRequest = {
147
+ runId: string;
148
+ requestId: string;
149
+ targetSessionId: string;
150
+ actor: MutationActor;
151
+ source: string;
152
+ prompt: string;
153
+ };
154
+
155
+ export type WorkflowRemoveFollowUpRequest = {
156
+ runId: string;
157
+ followUpId: string;
158
+ actor: MutationActor;
159
+ source: string;
160
+ };
161
+
162
+ export function workflowSettingsScopeId(
163
+ originRunId: string,
164
+ mountPath: string,
165
+ invocation: number,
166
+ ): string {
167
+ if (!Number.isInteger(invocation) || invocation <= 0) {
168
+ throw new Error("Workflow settings invocation must be a positive integer");
169
+ }
170
+ return `${originRunId}:${mountPath || "$root"}:${invocation}`;
171
+ }
172
+
173
+ export function workflowSettings<TSettings, TInput = unknown>(
174
+ options: WorkflowSettingsOptions<TSettings, TInput>,
175
+ ): WorkflowSettingsDefinition<TSettings, TInput> {
176
+ if (typeof options.parse !== "function") {
177
+ throw new Error("Invalid workflow settings: parse must be a function");
178
+ }
179
+ if (!Array.isArray(options.paths)) {
180
+ throw new Error("Invalid workflow settings: paths must be an array");
181
+ }
182
+ validatePathRules(options.paths);
183
+ if (options.description !== undefined && options.description.trim().length === 0) {
184
+ throw new Error("Invalid workflow settings: description must not be empty");
185
+ }
186
+ if (options.validateChange !== undefined && typeof options.validateChange !== "function") {
187
+ throw new Error("Invalid workflow settings: validateChange must be a function");
188
+ }
189
+ return Object.freeze({
190
+ ...options,
191
+ paths: Object.freeze(options.paths.map((rule) => freezeRule(rule))),
192
+ });
193
+ }
194
+
195
+ export function allowSettingsPath(
196
+ path: string,
197
+ permissions: WorkflowSettingsPathPermissions,
198
+ ): WorkflowSettingsPathRule {
199
+ parseJsonPointer(path);
200
+ validatePathPermissions(path, permissions);
201
+ return { path, permissions };
202
+ }
203
+
204
+ export function settingsRoute(
205
+ definition: Omit<ComputeNodeDefinition, "nodeType" | "settingsRoute">,
206
+ ): ComputeNodeDefinition {
207
+ if (typeof definition.run !== "function") {
208
+ throw new Error("Invalid settings route: run must be a function");
209
+ }
210
+ return { nodeType: "compute", settingsRoute: true, ...definition };
211
+ }
212
+
213
+ export async function resolveInitialWorkflowSettings<TSettings, TInput>(
214
+ definition: WorkflowSettingsDefinition<TSettings, TInput>,
215
+ input: TInput,
216
+ ): Promise<AppliedWorkflowSettings<TSettings>> {
217
+ const raw =
218
+ typeof definition.initial === "function"
219
+ ? await (definition.initial as (value: TInput) => MaybePromise<TSettings>)(input)
220
+ : definition.initial;
221
+ return await parseWorkflowSettingsValue(definition, raw);
222
+ }
223
+
224
+ export async function parseWorkflowSettingsValue<TSettings, TInput = unknown>(
225
+ definition: WorkflowSettingsDefinition<TSettings, TInput>,
226
+ raw: unknown,
227
+ ): Promise<AppliedWorkflowSettings<TSettings>> {
228
+ const parsed = await definition.parse(raw);
229
+ const json = cloneJson(parsed as JsonValue);
230
+ assertSettingsSize(json);
231
+ return { patch: [], settings: parsed, json };
232
+ }
233
+
234
+ export async function applyWorkflowSettingsPatch<TSettings, TInput = unknown>(
235
+ definition: WorkflowSettingsDefinition<TSettings, TInput>,
236
+ before: JsonValue,
237
+ patchValue: unknown,
238
+ actor: MutationActor,
239
+ source: string,
240
+ ): Promise<AppliedWorkflowSettings<TSettings>> {
241
+ const patch = validateJsonPatch(patchValue);
242
+ let current = cloneJson(before);
243
+ for (const operation of patch) {
244
+ authorizeOperation(definition.paths, current, operation, actor.type);
245
+ current = applyJsonPatchOperation(current, operation);
246
+ }
247
+ assertSettingsSize(current);
248
+ const parsed = await definition.parse(cloneJson(current));
249
+ const normalized = cloneJson(parsed as JsonValue);
250
+ assertSettingsSize(normalized);
251
+ if (canonicalJson(normalized) !== canonicalJson(current)) {
252
+ throw new Error("Workflow settings parser must validate without changing the patched value");
253
+ }
254
+ await definition.validateChange?.({
255
+ before: cloneJson(before) as unknown as TSettings,
256
+ after: parsed,
257
+ actor,
258
+ source,
259
+ patch,
260
+ });
261
+ return { patch, settings: parsed, json: normalized };
262
+ }
263
+
264
+ export function settingsRuleForPath(
265
+ rules: readonly WorkflowSettingsPathRule[],
266
+ pointer: string,
267
+ ): WorkflowSettingsPathRule | undefined {
268
+ const path = parseJsonPointer(pointer);
269
+ let match: WorkflowSettingsPathRule | undefined;
270
+ let matchLength = -1;
271
+ for (const rule of rules) {
272
+ const prefix = parseJsonPointer(rule.path);
273
+ if (prefix.length >= matchLength && prefix.every((segment, index) => path[index] === segment)) {
274
+ if (prefix.length > matchLength) {
275
+ match = rule;
276
+ matchLength = prefix.length;
277
+ }
278
+ }
279
+ }
280
+ return match;
281
+ }
282
+
283
+ function authorizeOperation(
284
+ rules: readonly WorkflowSettingsPathRule[],
285
+ document: JsonValue,
286
+ operation: JsonPatchOperation,
287
+ actor: ActorType,
288
+ ): void {
289
+ switch (operation.op) {
290
+ case "test":
291
+ requirePermission(rules, operation.path, "read", actor);
292
+ return;
293
+ case "add":
294
+ requirePermission(
295
+ rules,
296
+ operation.path,
297
+ destinationPermission(document, operation.path),
298
+ actor,
299
+ );
300
+ return;
301
+ case "remove":
302
+ requirePermission(rules, operation.path, "remove", actor);
303
+ return;
304
+ case "replace":
305
+ requirePermission(rules, operation.path, "replace", actor);
306
+ return;
307
+ case "copy":
308
+ requirePermission(rules, operation.from, "read", actor);
309
+ requirePermission(
310
+ rules,
311
+ operation.path,
312
+ destinationPermission(document, operation.path),
313
+ actor,
314
+ );
315
+ return;
316
+ case "move":
317
+ requirePermission(rules, operation.from, "read", actor);
318
+ requirePermission(rules, operation.from, "remove", actor);
319
+ requirePermission(
320
+ rules,
321
+ operation.path,
322
+ destinationPermission(document, operation.path),
323
+ actor,
324
+ );
325
+ return;
326
+ }
327
+ }
328
+
329
+ function destinationPermission(document: JsonValue, pointer: string): "add" | "replace" {
330
+ const segments = parseJsonPointer(pointer);
331
+ if (segments.length === 0) return "replace";
332
+ const parentPointer =
333
+ segments.length === 1 ? "" : `/${segments.slice(0, -1).map(encodeSegment).join("/")}`;
334
+ const parent = jsonPointerTarget(document, parentPointer);
335
+ if (!parent.exists) return "add";
336
+ if (Array.isArray(parent.value)) return "add";
337
+ return jsonPointerTarget(document, pointer).exists ? "replace" : "add";
338
+ }
339
+
340
+ function requirePermission(
341
+ rules: readonly WorkflowSettingsPathRule[],
342
+ pointer: string,
343
+ permission: WorkflowSettingsPermission,
344
+ actor: ActorType,
345
+ ): void {
346
+ const rule = settingsRuleForPath(rules, pointer);
347
+ if (rule?.permissions[permission]?.includes(actor) !== true) {
348
+ throw new Error(
349
+ `Workflow settings ${permission} is not allowed at ${JSON.stringify(pointer)} for actor ${actor}`,
350
+ );
351
+ }
352
+ }
353
+
354
+ function validatePathRules(rules: readonly WorkflowSettingsPathRule[]): void {
355
+ const byPath = new Map<string, WorkflowSettingsPathRule>();
356
+ for (const rule of rules) {
357
+ if (rule === null || typeof rule !== "object" || Array.isArray(rule)) {
358
+ throw new Error("Invalid workflow settings: each path rule must be an object");
359
+ }
360
+ parseJsonPointer(rule.path);
361
+ validatePathPermissions(rule.path, rule.permissions);
362
+ const normalized = freezeRule(rule);
363
+ const prior = byPath.get(rule.path);
364
+ if (
365
+ prior !== undefined &&
366
+ canonicalJson(prior.permissions) !== canonicalJson(normalized.permissions)
367
+ ) {
368
+ throw new Error(`Conflicting workflow settings rules for ${JSON.stringify(rule.path)}`);
369
+ }
370
+ byPath.set(rule.path, normalized);
371
+ }
372
+ }
373
+
374
+ function validatePathPermissions(path: string, permissions: WorkflowSettingsPathPermissions): void {
375
+ if (permissions === null || typeof permissions !== "object" || Array.isArray(permissions)) {
376
+ throw new Error(`Workflow settings path ${JSON.stringify(path)} permissions must be an object`);
377
+ }
378
+ if (Object.keys(permissions).length === 0) {
379
+ throw new Error(`Workflow settings path ${JSON.stringify(path)} grants no permissions`);
380
+ }
381
+ for (const [permission, actors] of Object.entries(permissions)) {
382
+ if (!isSettingsPermission(permission)) {
383
+ throw new Error(`Unknown workflow settings permission: ${permission}`);
384
+ }
385
+ if (!Array.isArray(actors) || actors.length === 0) {
386
+ throw new Error(
387
+ `Workflow settings permission ${permission} at ${JSON.stringify(path)} requires actors`,
388
+ );
389
+ }
390
+ for (const actor of actors) assertActorType(actor);
391
+ }
392
+ }
393
+
394
+ function freezeRule(rule: WorkflowSettingsPathRule): WorkflowSettingsPathRule {
395
+ const permissions: WorkflowSettingsPathPermissions = {};
396
+ for (const [permission, actors] of Object.entries(rule.permissions)) {
397
+ if (!isSettingsPermission(permission) || !Array.isArray(actors)) continue;
398
+ permissions[permission] = Object.freeze([...new Set(actors)]);
399
+ }
400
+ return Object.freeze({ path: rule.path, permissions: Object.freeze(permissions) });
401
+ }
402
+
403
+ function assertSettingsSize(value: JsonValue): void {
404
+ if (Buffer.byteLength(canonicalJson(value), "utf8") > MAX_WORKFLOW_SETTINGS_BYTES) {
405
+ throw new Error(`Workflow settings cannot exceed ${MAX_WORKFLOW_SETTINGS_BYTES} bytes`);
406
+ }
407
+ }
408
+
409
+ function assertActorType(value: unknown): asserts value is ActorType {
410
+ if (
411
+ value !== "session" &&
412
+ value !== "host" &&
413
+ value !== "controller" &&
414
+ value !== "channel" &&
415
+ value !== "human" &&
416
+ value !== "policy" &&
417
+ value !== "control" &&
418
+ value !== "system"
419
+ ) {
420
+ throw new Error(`Unknown workflow settings actor: ${JSON.stringify(value)}`);
421
+ }
422
+ }
423
+
424
+ function isSettingsPermission(value: string): value is WorkflowSettingsPermission {
425
+ return value === "read" || value === "add" || value === "remove" || value === "replace";
426
+ }
427
+
428
+ function encodeSegment(segment: string): string {
429
+ return segment.replaceAll("~", "~0").replaceAll("/", "~1");
430
+ }