@harny/sdk 0.1.2 → 0.1.4
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/dist/cjs/agent/adapters.d.ts +167 -0
- package/dist/cjs/agent/adapters.js +557 -0
- package/dist/cjs/agent/adapters.js.map +1 -0
- package/dist/cjs/agent/hooks.d.ts +52 -0
- package/dist/cjs/agent/hooks.js +41 -0
- package/dist/cjs/agent/hooks.js.map +1 -0
- package/dist/cjs/agent/index.d.ts +34 -0
- package/dist/cjs/agent/index.js +62 -0
- package/dist/cjs/agent/index.js.map +1 -0
- package/dist/cjs/agent/loop.d.ts +401 -0
- package/dist/cjs/agent/loop.js +1252 -0
- package/dist/cjs/agent/loop.js.map +1 -0
- package/dist/cjs/agent/roster.d.ts +62 -0
- package/dist/cjs/agent/roster.js +149 -0
- package/dist/cjs/agent/roster.js.map +1 -0
- package/dist/cjs/agent/session.d.ts +106 -0
- package/dist/cjs/agent/session.js +261 -0
- package/dist/cjs/agent/session.js.map +1 -0
- package/dist/cjs/agent/spend.d.ts +39 -0
- package/dist/cjs/agent/spend.js +70 -0
- package/dist/cjs/agent/spend.js.map +1 -0
- package/dist/cjs/agent/tools.d.ts +83 -0
- package/dist/cjs/agent/tools.js +212 -0
- package/dist/cjs/agent/tools.js.map +1 -0
- package/dist/cjs/agent/tracing.d.ts +72 -0
- package/dist/cjs/agent/tracing.js +128 -0
- package/dist/cjs/agent/tracing.js.map +1 -0
- package/dist/cjs/agent/util.d.ts +60 -0
- package/dist/cjs/agent/util.js +188 -0
- package/dist/cjs/agent/util.js.map +1 -0
- package/dist/cjs/conflictQuestion.d.ts +137 -0
- package/dist/cjs/conflictQuestion.js +356 -0
- package/dist/cjs/conflictQuestion.js.map +1 -0
- package/dist/cjs/index.d.ts +950 -0
- package/dist/cjs/index.js +1124 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/memoryV1.d.ts +121 -0
- package/dist/cjs/memoryV1.js +132 -0
- package/dist/cjs/memoryV1.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/esm/agent/adapters.d.ts +167 -0
- package/dist/esm/agent/adapters.js +543 -0
- package/dist/esm/agent/adapters.js.map +1 -0
- package/dist/esm/agent/hooks.d.ts +52 -0
- package/dist/esm/agent/hooks.js +36 -0
- package/dist/esm/agent/hooks.js.map +1 -0
- package/dist/esm/agent/index.d.ts +34 -0
- package/dist/esm/agent/index.js +28 -0
- package/dist/esm/agent/index.js.map +1 -0
- package/dist/esm/agent/loop.d.ts +401 -0
- package/dist/esm/agent/loop.js +1243 -0
- package/dist/esm/agent/loop.js.map +1 -0
- package/dist/esm/agent/roster.d.ts +62 -0
- package/dist/esm/agent/roster.js +143 -0
- package/dist/esm/agent/roster.js.map +1 -0
- package/dist/esm/agent/session.d.ts +106 -0
- package/dist/esm/agent/session.js +221 -0
- package/dist/esm/agent/session.js.map +1 -0
- package/dist/esm/agent/spend.d.ts +39 -0
- package/dist/esm/agent/spend.js +65 -0
- package/dist/esm/agent/spend.js.map +1 -0
- package/dist/esm/agent/tools.d.ts +83 -0
- package/dist/esm/agent/tools.js +205 -0
- package/dist/esm/agent/tools.js.map +1 -0
- package/dist/esm/agent/tracing.d.ts +72 -0
- package/dist/esm/agent/tracing.js +119 -0
- package/dist/esm/agent/tracing.js.map +1 -0
- package/dist/esm/agent/util.d.ts +60 -0
- package/dist/esm/agent/util.js +168 -0
- package/dist/esm/agent/util.js.map +1 -0
- package/dist/esm/conflictQuestion.d.ts +137 -0
- package/dist/esm/conflictQuestion.js +346 -0
- package/dist/esm/conflictQuestion.js.map +1 -0
- package/dist/esm/index.d.ts +950 -0
- package/dist/esm/index.js +1072 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/memoryV1.d.ts +121 -0
- package/dist/esm/memoryV1.js +124 -0
- package/dist/esm/memoryV1.js.map +1 -0
- package/dist/esm/package.json +1 -0
- package/package.json +3 -4
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Small helpers the agent modules share. Nothing here reaches the network.
|
|
4
|
+
*
|
|
5
|
+
* `pyDumps` writes JSON the way the Python SDK's agent does (", " and ": "
|
|
6
|
+
* separators), so a tool result or a quoted title reads byte for byte the
|
|
7
|
+
* same to the business's model whichever SDK runs the agent.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.RunAborted = exports.clock = exports.MAX_TIMER_MS = exports.INSPECT = void 0;
|
|
11
|
+
exports.sha256Hex = sha256Hex;
|
|
12
|
+
exports.toHex = toHex;
|
|
13
|
+
exports.randomHex = randomHex;
|
|
14
|
+
exports.newRunId = newRunId;
|
|
15
|
+
exports.asData = asData;
|
|
16
|
+
exports.isPlainObject = isPlainObject;
|
|
17
|
+
exports.truthy = truthy;
|
|
18
|
+
exports.or = or;
|
|
19
|
+
exports.toInt = toInt;
|
|
20
|
+
exports.clipChars = clipChars;
|
|
21
|
+
exports.pyDumps = pyDumps;
|
|
22
|
+
exports.quoted = quoted;
|
|
23
|
+
exports.plainCopy = plainCopy;
|
|
24
|
+
exports.pyIsoformat = pyIsoformat;
|
|
25
|
+
exports.sleep = sleep;
|
|
26
|
+
exports.processEnv = processEnv;
|
|
27
|
+
/** Hex SHA-256 of a UTF-8 string (WebCrypto, built into Node 20+). */
|
|
28
|
+
async function sha256Hex(text) {
|
|
29
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
|
|
30
|
+
return toHex(new Uint8Array(digest));
|
|
31
|
+
}
|
|
32
|
+
function toHex(bytes) {
|
|
33
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
34
|
+
}
|
|
35
|
+
/** `bytes` random bytes as lowercase hex. */
|
|
36
|
+
function randomHex(bytes) {
|
|
37
|
+
const buffer = new Uint8Array(bytes);
|
|
38
|
+
globalThis.crypto.getRandomValues(buffer);
|
|
39
|
+
return toHex(buffer);
|
|
40
|
+
}
|
|
41
|
+
/** A random 32-hex run id (a v4 UUID without dashes). */
|
|
42
|
+
function newRunId() {
|
|
43
|
+
if (typeof globalThis.crypto?.randomUUID === "function") {
|
|
44
|
+
return globalThis.crypto.randomUUID().replace(/-/g, "");
|
|
45
|
+
}
|
|
46
|
+
return randomHex(16);
|
|
47
|
+
}
|
|
48
|
+
/** Text placed inside a <context>, <actions> or <email> wrapper cannot close it. */
|
|
49
|
+
function asData(text) {
|
|
50
|
+
return text.replace(/</g, "<").replace(/>/g, ">");
|
|
51
|
+
}
|
|
52
|
+
function isPlainObject(value) {
|
|
53
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
54
|
+
}
|
|
55
|
+
/** Truthiness as the Python agent reads it: empty arrays and objects are false. */
|
|
56
|
+
function truthy(value) {
|
|
57
|
+
if (value === null || value === undefined || value === false || value === 0 || value === "")
|
|
58
|
+
return false;
|
|
59
|
+
if (Array.isArray(value))
|
|
60
|
+
return value.length > 0;
|
|
61
|
+
if (isPlainObject(value))
|
|
62
|
+
return Object.keys(value).length > 0;
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
/** `value` if truthy, else `fallback`. */
|
|
66
|
+
function or(value, fallback) {
|
|
67
|
+
return truthy(value) ? value : fallback;
|
|
68
|
+
}
|
|
69
|
+
/** Whole-number conversion that never throws (`0` for anything unreadable). */
|
|
70
|
+
function toInt(value) {
|
|
71
|
+
const n = typeof value === "number" ? value : Number(value);
|
|
72
|
+
return Number.isFinite(n) ? Math.trunc(n) : 0;
|
|
73
|
+
}
|
|
74
|
+
/** The first `limit` characters (code points) of `text`. */
|
|
75
|
+
function clipChars(text, limit) {
|
|
76
|
+
const chars = Array.from(text);
|
|
77
|
+
return chars.length <= limit ? text : chars.slice(0, limit).join("");
|
|
78
|
+
}
|
|
79
|
+
function jsonString(text, ensureAscii) {
|
|
80
|
+
const out = JSON.stringify(text);
|
|
81
|
+
if (!ensureAscii)
|
|
82
|
+
return out;
|
|
83
|
+
return out.replace(/[\u007f-]/g, (c) => "\\u" + c.charCodeAt(0).toString(16).padStart(4, "0"));
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* JSON with the Python SDK's default separators. `undefined` is written as
|
|
87
|
+
* `null`; a `Date` as its ISO string.
|
|
88
|
+
*/
|
|
89
|
+
function pyDumps(value, ensureAscii = false) {
|
|
90
|
+
if (value === null || value === undefined)
|
|
91
|
+
return "null";
|
|
92
|
+
if (typeof value === "string")
|
|
93
|
+
return jsonString(value, ensureAscii);
|
|
94
|
+
if (typeof value === "boolean")
|
|
95
|
+
return value ? "true" : "false";
|
|
96
|
+
if (typeof value === "number") {
|
|
97
|
+
if (Number.isNaN(value))
|
|
98
|
+
return "NaN";
|
|
99
|
+
if (!Number.isFinite(value))
|
|
100
|
+
return value > 0 ? "Infinity" : "-Infinity";
|
|
101
|
+
return String(value);
|
|
102
|
+
}
|
|
103
|
+
if (typeof value === "bigint")
|
|
104
|
+
return value.toString();
|
|
105
|
+
if (value instanceof Date)
|
|
106
|
+
return jsonString(value.toISOString(), ensureAscii);
|
|
107
|
+
if (Array.isArray(value))
|
|
108
|
+
return "[" + value.map((v) => pyDumps(v, ensureAscii)).join(", ") + "]";
|
|
109
|
+
if (typeof value === "object") {
|
|
110
|
+
const parts = Object.entries(value).map(([k, v]) => `${jsonString(k, ensureAscii)}: ${pyDumps(v, ensureAscii)}`);
|
|
111
|
+
return "{" + parts.join(", ") + "}";
|
|
112
|
+
}
|
|
113
|
+
return jsonString(String(value), ensureAscii);
|
|
114
|
+
}
|
|
115
|
+
/** A string quoted the way the Python agent names it in a message ('x'). */
|
|
116
|
+
function quoted(text) {
|
|
117
|
+
return `'${text}'`;
|
|
118
|
+
}
|
|
119
|
+
/** A deep copy through JSON (what a tool call's arguments are made of). */
|
|
120
|
+
function plainCopy(value) {
|
|
121
|
+
return JSON.parse(JSON.stringify(value ?? null));
|
|
122
|
+
}
|
|
123
|
+
/** Node's hook for how `console.log` and `util.inspect` show an object. */
|
|
124
|
+
exports.INSPECT = Symbol.for("nodejs.util.inspect.custom");
|
|
125
|
+
const ISO_DATETIME = /^(\d{4}-\d{2}-\d{2})[T ](\d{2}:\d{2}:\d{2})(?:\.(\d+))?(Z|[+-]\d{2}:?\d{2})?$/;
|
|
126
|
+
/**
|
|
127
|
+
* A date-time string as the Python SDK writes it after reading it
|
|
128
|
+
* (`datetime.isoformat()`): `Z` becomes `+00:00`, fractions are six digits or
|
|
129
|
+
* none. Anything that is not a date-time passes through unchanged.
|
|
130
|
+
*/
|
|
131
|
+
function pyIsoformat(value) {
|
|
132
|
+
if (typeof value !== "string")
|
|
133
|
+
return value;
|
|
134
|
+
const match = ISO_DATETIME.exec(value.trim());
|
|
135
|
+
if (!match)
|
|
136
|
+
return value;
|
|
137
|
+
let fraction = match[3] ? (match[3] + "000000").slice(0, 6) : "";
|
|
138
|
+
if (/^0*$/.test(fraction))
|
|
139
|
+
fraction = "";
|
|
140
|
+
let zone = match[4] ?? "";
|
|
141
|
+
if (zone === "Z" || zone === "-00:00" || zone === "-0000")
|
|
142
|
+
zone = "+00:00";
|
|
143
|
+
else if (zone && !zone.includes(":"))
|
|
144
|
+
zone = `${zone.slice(0, 3)}:${zone.slice(3)}`;
|
|
145
|
+
return `${match[1]}T${match[2]}${fraction ? "." + fraction : ""}${zone}`;
|
|
146
|
+
}
|
|
147
|
+
/** The longest delay a timer can hold; a larger one would fire at once. */
|
|
148
|
+
exports.MAX_TIMER_MS = 2147483647;
|
|
149
|
+
/** Resolve after `ms`, or reject at once when `signal` aborts. */
|
|
150
|
+
function sleep(ms, signal) {
|
|
151
|
+
return new Promise((resolve, reject) => {
|
|
152
|
+
if (signal?.aborted) {
|
|
153
|
+
reject(new RunAborted());
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const timer = setTimeout(() => {
|
|
157
|
+
signal?.removeEventListener("abort", onAbort);
|
|
158
|
+
resolve();
|
|
159
|
+
}, Math.min(ms, exports.MAX_TIMER_MS));
|
|
160
|
+
const onAbort = () => {
|
|
161
|
+
clearTimeout(timer);
|
|
162
|
+
reject(new RunAborted());
|
|
163
|
+
};
|
|
164
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* The loop's clock: milliseconds on a steady clock, and a wait the run's
|
|
169
|
+
* time can cut short. Tests replace these two; nothing else should.
|
|
170
|
+
*/
|
|
171
|
+
exports.clock = {
|
|
172
|
+
now: () => (typeof globalThis.performance?.now === "function" ? globalThis.performance.now() : Date.now()),
|
|
173
|
+
sleep: (ms, signal) => sleep(ms, signal),
|
|
174
|
+
};
|
|
175
|
+
/** Thrown inside a run that already stopped (its time ran out). Never reaches the caller. */
|
|
176
|
+
class RunAborted extends Error {
|
|
177
|
+
constructor() {
|
|
178
|
+
super("the run stopped");
|
|
179
|
+
this.name = "RunAborted";
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
exports.RunAborted = RunAborted;
|
|
183
|
+
/** The process environment when there is one (Node), else an empty map. */
|
|
184
|
+
function processEnv() {
|
|
185
|
+
const proc = globalThis.process;
|
|
186
|
+
return proc?.env ?? {};
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=util.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.js","sourceRoot":"","sources":["../../../src/agent/util.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What to ask a person whose notes conflict, and the rule for when. Pure: no network, no model call, no clock.
|
|
3
|
+
*
|
|
4
|
+
* A person can say two things that cannot both be true ("Maya prefers email", weeks later "Maya prefers phone").
|
|
5
|
+
* Harny keeps both and picks neither; the context response returns them in
|
|
6
|
+
* `conflicts`. Harny adds no pause of its own. The question is YOUR confirm step's: call
|
|
7
|
+
* `decideConflictQuestion` before an action, show `text` if it says `ask`, tell Harny you asked
|
|
8
|
+
* (`client.markConflictAsked`), and send the person's answer (`client.answerConflict`). The answer is saved as
|
|
9
|
+
* something the person said, and the conflict stops being returned.
|
|
10
|
+
*
|
|
11
|
+
* const result = await client.assembleContext("send Maya the update", undefined, { user });
|
|
12
|
+
* const decision = decideConflictQuestion({
|
|
13
|
+
* conflicts: result.conflicts,
|
|
14
|
+
* dependsOn: conflictsTheSendDependsOn.map((c) => c.record_id_1), // the ones whose answer changes what is sent
|
|
15
|
+
* actionKinds: ["others_affected"], // the kinds of action this is
|
|
16
|
+
* mode: result.conflict_question_mode, // the app's "When notes conflict" setting
|
|
17
|
+
* phrases: new QuestionPhrases({ action: "send", question: "Does Maya prefer email or phone?" }),
|
|
18
|
+
* });
|
|
19
|
+
* if (decision.ask) {
|
|
20
|
+
* const claim = await client.markConflictAsked(decision, { user }); // claim the question FIRST
|
|
21
|
+
* if (claim.first_time) show(decision.text); // only the first claim shows it
|
|
22
|
+
* // (in your own confirm card or chat)
|
|
23
|
+
* }
|
|
24
|
+
*
|
|
25
|
+
* Report before you show, and show only when `first_time` is true: the first report wins, so a second action in the same
|
|
26
|
+
* turn, a retry or a second process cannot put the question twice. (A decision by itself changes nothing: `ledger` and
|
|
27
|
+
* `record` only help a host that keeps its own memory.)
|
|
28
|
+
*
|
|
29
|
+
* THE RULE (all six must hold, checked in this order; the first that fails names itself in `reason`):
|
|
30
|
+
*
|
|
31
|
+
* 1. the action is of the "others affected" or "irreversible" kind (`ASKING_KINDS`): an action that only touches
|
|
32
|
+
* the person's own things never asks;
|
|
33
|
+
* 2. a conflict exists for a note the action depends on. `dependsOn` lists the `record_id_1` / `record_id_2` of each
|
|
34
|
+
* conflict whose answer changes what the action does; you (or your agent) decide that from the two statements;
|
|
35
|
+
* 3. the question is on (`mode` is not null or undefined). The response names the app's mode only while Harny has
|
|
36
|
+
* the question on, so no mode with a conflict in hand means do not ask;
|
|
37
|
+
* 4. the person did not say both in one conversation (`same_conversation` is false): they have just said it, so
|
|
38
|
+
* they are never asked about it there;
|
|
39
|
+
* 5. that conflict has not been asked about before: at most once per conflict. It counts as asked when the
|
|
40
|
+
* response marked it `asked` or your `ledger` has it;
|
|
41
|
+
* 6. the wording the question needs was supplied FOR THAT CONFLICT (a lone `QuestionPhrases` is accepted only while
|
|
42
|
+
* one conflict can be asked; with several, pass a function of the conflict or a map by `conflictKey`).
|
|
43
|
+
*
|
|
44
|
+
* THE WORDS depend on the app's "When notes conflict" setting: `simple` (the default, no dates), `precise` (both
|
|
45
|
+
* statements with the day each was said) and `handoff` (a short question that offers the detail, which your own
|
|
46
|
+
* interface shows from the `conflicts` data). The sentences carry natural-language phrases ("Maya prefers email") that the stored statements do not, so YOU supply them in `QuestionPhrases`; this module never writes
|
|
47
|
+
* wording of its own: a missing phrase is a `missing_phrases` refusal, not a guess.
|
|
48
|
+
*/
|
|
49
|
+
/** The closed set the app's "When notes conflict" setting takes. */
|
|
50
|
+
export declare const CONFLICT_QUESTION_MODES: readonly ["simple", "precise", "handoff"];
|
|
51
|
+
export type ConflictQuestionMode = (typeof CONFLICT_QUESTION_MODES)[number];
|
|
52
|
+
export declare const DEFAULT_CONFLICT_QUESTION_MODE: ConflictQuestionMode;
|
|
53
|
+
/** The action kinds that may ask. `money` and `nobody_else` do not. */
|
|
54
|
+
export declare const ASKING_KINDS: ReadonlySet<string>;
|
|
55
|
+
/** One conflict as the context response carries it (a typed item or a plain object). */
|
|
56
|
+
export type ConflictLike = Record<string, any>;
|
|
57
|
+
export interface QuestionPhrasesInit {
|
|
58
|
+
action?: string;
|
|
59
|
+
question?: string;
|
|
60
|
+
claim_1?: string;
|
|
61
|
+
claim_2?: string;
|
|
62
|
+
topic?: string;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The natural-language pieces YOU write about THIS conflict.
|
|
66
|
+
*
|
|
67
|
+
* `action`: the verb of what is about to happen ("send", "reply", "share"); all three modes. `question`: a complete
|
|
68
|
+
* yes/no question about the fact, for `simple` ("Does Maya prefer email or phone?"). `claim_1` /
|
|
69
|
+
* `claim_2`: the two statements as they read after "you said" and "that", for `precise` ("Maya prefers email", "she prefers phone"). `topic`: what the notes conflict about, for `handoff` ("Maya's contact preference").
|
|
70
|
+
*/
|
|
71
|
+
export declare class QuestionPhrases {
|
|
72
|
+
readonly action: string;
|
|
73
|
+
readonly question: string;
|
|
74
|
+
readonly claim_1: string;
|
|
75
|
+
readonly claim_2: string;
|
|
76
|
+
readonly topic: string;
|
|
77
|
+
constructor(init?: QuestionPhrasesInit);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The words for the conflict being asked about: one `QuestionPhrases` (only while a single conflict can be asked),
|
|
81
|
+
* a function of the conflict item, or a map from `conflictKey` (a `Map` or a plain object).
|
|
82
|
+
*/
|
|
83
|
+
export type PhrasesSource = QuestionPhrases | QuestionPhrasesInit | ((conflict: ConflictLike) => QuestionPhrases | null | undefined) | ReadonlyMap<string, QuestionPhrases> | Readonly<Record<string, QuestionPhrases>>;
|
|
84
|
+
/** Where YOUR app remembers which conflicts it has asked about, besides Harny's own memory. Optional. */
|
|
85
|
+
export interface ConflictAskLedger {
|
|
86
|
+
has(key: string): boolean;
|
|
87
|
+
add(key: string): void;
|
|
88
|
+
}
|
|
89
|
+
/** A ledger held in memory: for tests and for a first draft. */
|
|
90
|
+
export declare class InMemoryConflictAskLedger implements ConflictAskLedger {
|
|
91
|
+
private readonly asked;
|
|
92
|
+
constructor(asked?: Iterable<string>);
|
|
93
|
+
has(key: string): boolean;
|
|
94
|
+
add(key: string): void;
|
|
95
|
+
get size(): number;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* The decision: `ask` and, when true, the `text` to put to the person. `reason` is `asked` or the first rule that
|
|
99
|
+
* failed: `kind_not_eligible`, `no_conflict`, `rule_dark`, `same_conversation`, `already_asked`,
|
|
100
|
+
* `ambiguous_phrases` (one `QuestionPhrases` offered while several conflicts could be asked), `missing_phrases`.
|
|
101
|
+
* `key` / `conflict` name the conflict asked about (or, for a refusal that found one, the one it stopped on); pass
|
|
102
|
+
* the decision itself, or `conflict`, to `markConflictAsked`.
|
|
103
|
+
*/
|
|
104
|
+
export interface ConflictQuestion {
|
|
105
|
+
ask: boolean;
|
|
106
|
+
reason: string;
|
|
107
|
+
text: string | null;
|
|
108
|
+
key: string | null;
|
|
109
|
+
conflict: ConflictLike | null;
|
|
110
|
+
}
|
|
111
|
+
export interface DecideConflictQuestionOptions {
|
|
112
|
+
/** `result.conflicts` of the context response. */
|
|
113
|
+
conflicts: ReadonlyArray<ConflictLike> | null | undefined;
|
|
114
|
+
/** The record ids of the conflicts the action's result depends on. */
|
|
115
|
+
dependsOn: Iterable<string> | string;
|
|
116
|
+
/** The kinds of action this is. */
|
|
117
|
+
actionKinds: Iterable<string> | string;
|
|
118
|
+
/** `result.conflict_question_mode`. No mode (null or undefined) means the question is off: do not ask. */
|
|
119
|
+
mode: string | null | undefined;
|
|
120
|
+
phrases: PhrasesSource;
|
|
121
|
+
ledger?: ConflictAskLedger;
|
|
122
|
+
/** Add the conflict to `ledger` when this returns an ask. */
|
|
123
|
+
record?: boolean;
|
|
124
|
+
}
|
|
125
|
+
/** One of `CONFLICT_QUESTION_MODES`; anything else is the default (the plain, no-dates question). */
|
|
126
|
+
export declare function normaliseMode(value: unknown): ConflictQuestionMode;
|
|
127
|
+
/** `2024-09-22` -> `Sept 22`. An empty string when the value is not an ISO day. */
|
|
128
|
+
export declare function formatDay(isoDay: unknown): string;
|
|
129
|
+
/** The question for `mode`. Throws on a missing phrase or an unreadable said-day (`precise` only). */
|
|
130
|
+
export declare function questionText(mode: unknown, conflict: ConflictLike, phrases: QuestionPhrases): string;
|
|
131
|
+
/**
|
|
132
|
+
* The identity of one conflict: the two stored notes behind it, in either order. The same value Harny puts in
|
|
133
|
+
* `conflicts[].conflict_key`, stable across responses, so "asked once" holds however the list is ordered.
|
|
134
|
+
*/
|
|
135
|
+
export declare function conflictKey(conflict: ConflictLike): string;
|
|
136
|
+
/** Whether to ask now, and what to say. See the module comment for the rule. */
|
|
137
|
+
export declare function decideConflictQuestion(options: DecideConflictQuestionOptions): ConflictQuestion;
|