@north-light/api 0.0.0 → 0.3.414
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/LICENSE +18 -0
- package/README.md +66 -2
- package/dist/api/__tests__/error-codes.test.d.ts +1 -0
- package/dist/api/__tests__/error-codes.test.js +79 -0
- package/dist/api/__tests__/integration/client.test.d.ts +1 -0
- package/dist/api/__tests__/integration/client.test.js +276 -0
- package/dist/api/client.d.ts +501 -0
- package/dist/api/client.js +1281 -0
- package/dist/api/command-manifest/index.d.ts +3 -0
- package/dist/api/command-manifest/index.js +3 -0
- package/dist/api/command-manifest/manifest.d.ts +51 -0
- package/dist/api/command-manifest/manifest.js +332 -0
- package/dist/api/command-manifest/result.d.ts +25 -0
- package/dist/api/command-manifest/result.js +97 -0
- package/dist/api/command-manifest/schema.d.ts +28 -0
- package/dist/api/command-manifest/schema.js +856 -0
- package/dist/api/dto/analytics.d.ts +184 -0
- package/dist/api/dto/analytics.js +3 -0
- package/dist/api/dto/attach.d.ts +22 -0
- package/dist/api/dto/attach.js +13 -0
- package/dist/api/dto/bash-jobs.d.ts +24 -0
- package/dist/api/dto/bash-jobs.js +9 -0
- package/dist/api/dto/bash.d.ts +17 -0
- package/dist/api/dto/bash.js +1 -0
- package/dist/api/dto/broker-ops.d.ts +187 -0
- package/dist/api/dto/broker-ops.js +6 -0
- package/dist/api/dto/broker-signals.d.ts +25 -0
- package/dist/api/dto/broker-signals.js +1 -0
- package/dist/api/dto/broker.d.ts +86 -0
- package/dist/api/dto/broker.js +20 -0
- package/dist/api/dto/canvas.d.ts +359 -0
- package/dist/api/dto/canvas.js +2 -0
- package/dist/api/dto/chat-inventory.d.ts +56 -0
- package/dist/api/dto/chat-inventory.js +11 -0
- package/dist/api/dto/common.d.ts +29 -0
- package/dist/api/dto/common.js +15 -0
- package/dist/api/dto/config.d.ts +46 -0
- package/dist/api/dto/config.js +3 -0
- package/dist/api/dto/crons.d.ts +158 -0
- package/dist/api/dto/crons.js +10 -0
- package/dist/api/dto/custom-objects.d.ts +66 -0
- package/dist/api/dto/custom-objects.js +1 -0
- package/dist/api/dto/delivery.d.ts +71 -0
- package/dist/api/dto/delivery.js +7 -0
- package/dist/api/dto/docs.d.ts +135 -0
- package/dist/api/dto/docs.js +8 -0
- package/dist/api/dto/files.d.ts +21 -0
- package/dist/api/dto/files.js +1 -0
- package/dist/api/dto/focus.d.ts +24 -0
- package/dist/api/dto/focus.js +10 -0
- package/dist/api/dto/grants.d.ts +28 -0
- package/dist/api/dto/grants.js +1 -0
- package/dist/api/dto/health.d.ts +116 -0
- package/dist/api/dto/health.js +2 -0
- package/dist/api/dto/human-requests.d.ts +113 -0
- package/dist/api/dto/human-requests.js +4 -0
- package/dist/api/dto/human.d.ts +28 -0
- package/dist/api/dto/human.js +4 -0
- package/dist/api/dto/inbox.d.ts +273 -0
- package/dist/api/dto/inbox.js +4 -0
- package/dist/api/dto/lifecycle.d.ts +88 -0
- package/dist/api/dto/lifecycle.js +3 -0
- package/dist/api/dto/limits.d.ts +29 -0
- package/dist/api/dto/limits.js +2 -0
- package/dist/api/dto/mail.d.ts +44 -0
- package/dist/api/dto/mail.js +1 -0
- package/dist/api/dto/messages.d.ts +104 -0
- package/dist/api/dto/messages.js +2 -0
- package/dist/api/dto/model-config.d.ts +25 -0
- package/dist/api/dto/model-config.js +1 -0
- package/dist/api/dto/modelauth.d.ts +132 -0
- package/dist/api/dto/modelauth.js +4 -0
- package/dist/api/dto/node-events.d.ts +65 -0
- package/dist/api/dto/node-events.js +4 -0
- package/dist/api/dto/node-outcomes.d.ts +88 -0
- package/dist/api/dto/node-outcomes.js +2 -0
- package/dist/api/dto/node-records.d.ts +35 -0
- package/dist/api/dto/node-records.js +5 -0
- package/dist/api/dto/nodes.d.ts +371 -0
- package/dist/api/dto/nodes.js +3 -0
- package/dist/api/dto/objects.d.ts +172 -0
- package/dist/api/dto/objects.js +5 -0
- package/dist/api/dto/profiles.d.ts +117 -0
- package/dist/api/dto/profiles.js +4 -0
- package/dist/api/dto/recovery.d.ts +104 -0
- package/dist/api/dto/recovery.js +1 -0
- package/dist/api/dto/reports.d.ts +95 -0
- package/dist/api/dto/reports.js +2 -0
- package/dist/api/dto/review-comments.d.ts +146 -0
- package/dist/api/dto/review-comments.js +5 -0
- package/dist/api/dto/reviews.d.ts +113 -0
- package/dist/api/dto/reviews.js +5 -0
- package/dist/api/dto/run-events.d.ts +295 -0
- package/dist/api/dto/run-events.js +6 -0
- package/dist/api/dto/structured-requests.d.ts +64 -0
- package/dist/api/dto/structured-requests.js +5 -0
- package/dist/api/dto/subscriptions.d.ts +14 -0
- package/dist/api/dto/subscriptions.js +2 -0
- package/dist/api/dto/worktree.d.ts +55 -0
- package/dist/api/dto/worktree.js +6 -0
- package/dist/api/error-codes.d.ts +259 -0
- package/dist/api/error-codes.js +56 -0
- package/dist/api/errors.d.ts +47 -0
- package/dist/api/errors.js +66 -0
- package/dist/api/index.d.ts +48 -0
- package/dist/api/index.js +45 -0
- package/dist/api/long-poll.d.ts +59 -0
- package/dist/api/long-poll.js +67 -0
- package/dist/api/node-transport.d.ts +18 -0
- package/dist/api/node-transport.js +105 -0
- package/dist/api/plugin-manifest-schema.d.ts +233 -0
- package/dist/api/plugin-manifest-schema.js +23 -0
- package/dist/api/routes.d.ts +164 -0
- package/dist/api/routes.js +197 -0
- package/dist/api/runtime-version.d.ts +31 -0
- package/dist/api/runtime-version.js +50 -0
- package/dist/core/asset-root.d.ts +7 -0
- package/dist/core/asset-root.js +18 -0
- package/dist/core/canvas/boot-id.d.ts +6 -0
- package/dist/core/canvas/boot-id.js +26 -0
- package/dist/core/canvas/paths.d.ts +72 -0
- package/dist/core/canvas/paths.js +163 -0
- package/dist/core/canvas/pid.d.ts +391 -0
- package/dist/core/canvas/pid.js +948 -0
- package/dist/core/command-plugins/bundle.d.ts +149 -0
- package/dist/core/command-plugins/bundle.js +588 -0
- package/dist/core/command-plugins/endpoint.d.ts +24 -0
- package/dist/core/command-plugins/endpoint.js +51 -0
- package/dist/core/config.d.ts +233 -0
- package/dist/core/config.js +1120 -0
- package/dist/core/env-name.d.ts +6 -0
- package/dist/core/env-name.js +9 -0
- package/dist/core/errors.d.ts +38 -0
- package/dist/core/errors.js +90 -0
- package/dist/core/events/emit.d.ts +6 -0
- package/dist/core/events/emit.js +42 -0
- package/dist/core/events/envelope.d.ts +2 -0
- package/dist/core/events/envelope.js +84 -0
- package/dist/core/events/errors.d.ts +4 -0
- package/dist/core/events/errors.js +69 -0
- package/dist/core/events/operation-id.d.ts +4 -0
- package/dist/core/events/operation-id.js +24 -0
- package/dist/core/events/serialize.d.ts +4 -0
- package/dist/core/events/serialize.js +199 -0
- package/dist/core/events/source.d.ts +16 -0
- package/dist/core/events/source.js +31 -0
- package/dist/core/events/types.d.ts +68 -0
- package/dist/core/events/types.js +11 -0
- package/dist/core/exclusive-lock.d.ts +34 -0
- package/dist/core/exclusive-lock.js +197 -0
- package/dist/core/fs-utils.d.ts +44 -0
- package/dist/core/fs-utils.js +208 -0
- package/dist/core/help.d.ts +309 -0
- package/dist/core/help.js +406 -0
- package/dist/core/human/page-catalog.d.ts +57 -0
- package/dist/core/human/page-catalog.js +172 -0
- package/dist/core/installed-plugins.d.ts +2 -0
- package/dist/core/installed-plugins.js +79 -0
- package/dist/core/io.d.ts +122 -0
- package/dist/core/io.js +373 -0
- package/dist/core/keybindings/attach-control.d.ts +49 -0
- package/dist/core/keybindings/attach-control.js +42 -0
- package/dist/core/keybindings/catalog.d.ts +18 -0
- package/dist/core/keybindings/catalog.js +257 -0
- package/dist/core/keybindings/types.d.ts +42 -0
- package/dist/core/keybindings/types.js +1 -0
- package/dist/core/layout.d.ts +34 -0
- package/dist/core/layout.js +106 -0
- package/dist/core/locked-file.d.ts +27 -0
- package/dist/core/locked-file.js +118 -0
- package/dist/core/log.d.ts +9 -0
- package/dist/core/log.js +89 -0
- package/dist/core/manifest.d.ts +5 -0
- package/dist/core/manifest.js +15 -0
- package/dist/core/plugin-env.d.ts +8 -0
- package/dist/core/plugin-env.js +31 -0
- package/dist/core/plugin-extensions.d.ts +29 -0
- package/dist/core/plugin-extensions.js +191 -0
- package/dist/core/plugin-swap-lock.d.ts +9 -0
- package/dist/core/plugin-swap-lock.js +31 -0
- package/dist/core/preview-result-path.d.ts +4 -0
- package/dist/core/preview-result-path.js +26 -0
- package/dist/core/profiles/env-store.d.ts +22 -0
- package/dist/core/profiles/env-store.js +163 -0
- package/dist/core/profiles/fuzzy-match.d.ts +19 -0
- package/dist/core/profiles/fuzzy-match.js +92 -0
- package/dist/core/profiles/manifest.d.ts +120 -0
- package/dist/core/profiles/manifest.js +529 -0
- package/dist/core/rate-limit-scope.d.ts +25 -0
- package/dist/core/rate-limit-scope.js +64 -0
- package/dist/core/render.d.ts +12 -0
- package/dist/core/render.js +138 -0
- package/dist/core/resolver.d.ts +14 -0
- package/dist/core/resolver.js +111 -0
- package/dist/core/runtime/branded-host.d.ts +55 -0
- package/dist/core/runtime/branded-host.js +485 -0
- package/dist/core/runtime/broker/daemon-ops.d.ts +65 -0
- package/dist/core/runtime/broker/daemon-ops.js +177 -0
- package/dist/core/runtime/broker/signal-stream.d.ts +30 -0
- package/dist/core/runtime/broker/signal-stream.js +149 -0
- package/dist/core/scope.d.ts +32 -0
- package/dist/core/scope.js +184 -0
- package/dist/core/scoped-state/db.d.ts +17 -0
- package/dist/core/scoped-state/db.js +247 -0
- package/dist/core/scoped-state/migrate.d.ts +8 -0
- package/dist/core/scoped-state/migrate.js +187 -0
- package/dist/core/scoped-state/paths.d.ts +9 -0
- package/dist/core/scoped-state/paths.js +27 -0
- package/dist/core/scoped-state/profiles.d.ts +27 -0
- package/dist/core/scoped-state/profiles.js +93 -0
- package/dist/core/scoped-state/providers.d.ts +24 -0
- package/dist/core/scoped-state/providers.js +19 -0
- package/dist/core/scoped-state/schema.d.ts +6 -0
- package/dist/core/scoped-state/schema.js +43 -0
- package/dist/core/scoped-state/settings.d.ts +28 -0
- package/dist/core/scoped-state/settings.js +83 -0
- package/dist/core/spaces/open-beneath.d.ts +71 -0
- package/dist/core/spaces/open-beneath.js +581 -0
- package/dist/core/sqlite-statements.d.ts +4 -0
- package/dist/core/sqlite-statements.js +17 -0
- package/dist/core/subscription-state.d.ts +121 -0
- package/dist/core/subscription-state.js +287 -0
- package/dist/core/user-settings.d.ts +377 -0
- package/dist/core/user-settings.js +458 -0
- package/dist/daemon/broker-signals/bus.d.ts +30 -0
- package/dist/daemon/broker-signals/bus.js +87 -0
- package/dist/daemon/manage.d.ts +176 -0
- package/dist/daemon/manage.js +664 -0
- package/dist/daemon/pidfile.d.ts +8 -0
- package/dist/daemon/pidfile.js +37 -0
- package/dist/daemon/startup-policy.d.ts +1 -0
- package/dist/daemon/startup-policy.js +1 -0
- package/dist/native/linux.d.ts +29 -0
- package/dist/native/linux.js +20 -0
- package/dist/shared/env.d.ts +116 -0
- package/dist/shared/env.js +271 -0
- package/dist/shared/generated-context.d.ts +83 -0
- package/dist/shared/generated-context.js +235 -0
- package/dist/shared/inbox-entry-body.d.ts +22 -0
- package/dist/shared/inbox-entry-body.js +116 -0
- package/dist/shared/predicates.d.ts +2 -0
- package/dist/shared/predicates.js +4 -0
- package/dist/shared/working-activity.d.ts +9 -0
- package/dist/shared/working-activity.js +27 -0
- package/dist/types.d.ts +562 -0
- package/dist/types.js +186 -0
- package/package.json +46 -5
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
// Runtime-card grammar for crouter-authored context messages.
|
|
2
|
+
//
|
|
3
|
+
// Every runtime message carries one whole-message envelope. customType only
|
|
4
|
+
// controls delivery and visibility; parsing stays independent of runtime and clients.
|
|
5
|
+
/** Custom message carrying the node's session-start bearings. */
|
|
6
|
+
export const CONTEXT_INTRO_CUSTOM_TYPE = 'crtr-context';
|
|
7
|
+
/** Custom message carrying an ambient situational-context update. */
|
|
8
|
+
export const SITUATIONAL_CONTEXT_CUSTOM_TYPE = 'crtr-situational-context';
|
|
9
|
+
/** Custom message used when a context-size nudge waits for the next turn. */
|
|
10
|
+
export const CONTEXT_NUDGE_CUSTOM_TYPE = 'crtr-context-nudge';
|
|
11
|
+
/** Custom message that opens a review companion's visible transcript. */
|
|
12
|
+
export const REVIEW_BOUNDARY_CUSTOM_TYPE = 'crtr-review-boundary';
|
|
13
|
+
/** Generic completion mandate issued by the terminal-node stop guard. */
|
|
14
|
+
export const STALL_REPROMPT = "You've stopped but you're not waiting on anyone and haven't finished. " +
|
|
15
|
+
"Pipe the result to `crtr push final` through a single-quoted heredoc if the work is done, or use `crtr human send` if you are blocked or need the user.";
|
|
16
|
+
/** The daemon's parking mandate for an isolated turn. It leaves current truth,
|
|
17
|
+
* supporting material when needed, lasting lessons, and one deferred update;
|
|
18
|
+
* the main conversation remains untouched and resumes without a fresh cycle. */
|
|
19
|
+
export const PARK_SUMMARY_PROMPT = 'This conversation has been idle with nothing left to wake it, so it is being concluded. This is your last turn. Use it to leave a trustworthy inheritance, not to restart or broaden the work. Do these four things now, then stop.\n\n'
|
|
20
|
+
+ '1. Establish current truth. Check only state that may have changed outside the transcript and matters to resuming—such as the working tree, a remote run, or an external decision. Do not start new work; perform only a quick check needed to avoid recording an unverified claim. If no mandate or work ever began, record that plainly in the inheritance and keep every artifact minimal.\n\n'
|
|
21
|
+
+ '2. Put supporting material in documents you own (`crtr doc write`) only when it is needed to keep the inheritance concise. Rewrite existing living documents rather than leave superseded versions. Task state, identifiers, and recovery detail belong here, under this node, not under a longer-lived owner.\n\n'
|
|
22
|
+
+ '3. Give a longer-lived owner (`--owner repo|profile|user`) only to a non-obvious, reusable lesson that should survive this task and is not already recorded. Read `crtr doc write -h`, search before writing (`crtr canvas search`), and choose the narrowest owner that will reach the next agent who needs it. Do not put a conversation recap, task status, recovery handles, or facts already captured in code or docs into such a document.\n\n'
|
|
23
|
+
+ '4. Push exactly one regular update with `crtr push update --tier deferred`, never `crtr push final`. Write for someone who has not seen this conversation: name the work in plain terms, say where it stands, and say what remains or is blocked. Put the current outcome in the first line; include only needed decisions and recovery handles, then stop.';
|
|
24
|
+
/** Static recovery prompts shared by the broker producer and display classifier. */
|
|
25
|
+
export const AUTH_FAULT_RECOVERY_BODY = 'Provider credentials were just updated (a new login landed). Your previous turn stopped on a provider authentication failure. Continue from where you left off and retry the work that failed.';
|
|
26
|
+
export const CONNECTION_FAULT_RECOVERY_BODY = 'The network connection is back online. Your previous turn stopped on a connection error (the network was down). Continue from where you left off and retry the work that failed.';
|
|
27
|
+
/** A run node's connection retry. Its broker never probes the network (its
|
|
28
|
+
* model calls go through the daemon), so it cannot claim the network is back;
|
|
29
|
+
* the attempt itself is the probe. */
|
|
30
|
+
export const RUN_CONNECTION_FAULT_RETRY_BODY = 'Your previous turn stopped on a connection error reaching the model provider. Retrying now: continue from where you left off and retry the work that failed.';
|
|
31
|
+
export const PROVIDER_FAULT_RECOVERY_BODY = 'Your previous turn stopped on a provider fault. Continue from where you left off and retry the work that failed.';
|
|
32
|
+
const REVIEW_APPROVAL_OPEN = '<crtr-review-approval>';
|
|
33
|
+
const REVIEW_APPROVAL_CLOSE = '</crtr-review-approval>';
|
|
34
|
+
const MODEL_FALLBACK_RECOVERY_OPEN = '<model-fallback-recovery>';
|
|
35
|
+
const MODEL_FALLBACK_RECOVERY_CLOSE = '</model-fallback-recovery>';
|
|
36
|
+
const STRUCTURED_OUTPUT_REPROMPT_SUFFIX = '\n```';
|
|
37
|
+
/** Format the stop guard's dynamic structured-output mandate. */
|
|
38
|
+
export function formatStructuredOutputReprompt(schema, requestId) {
|
|
39
|
+
return `You must submit a result matching the required schema before you can stop: call the \`submit_result\` tool with \`request_id: "${requestId}"\` and the JSON in \`result\`, or with \`decline: { reason, code }\` when the schema cannot be honestly satisfied. You cannot finish or go dormant any other way while this request is pending.\n\nRequired schema:\n\n\`\`\`json\n${schema}${STRUCTURED_OUTPUT_REPROMPT_SUFFIX}`;
|
|
40
|
+
}
|
|
41
|
+
/** Keep model-fallback guidance editable without making it a reader contract. */
|
|
42
|
+
export function formatModelFallbackRecovery(previousModel, nextModel, reason) {
|
|
43
|
+
return reason === 'credential'
|
|
44
|
+
? `The previous model (${previousModel}) had no usable provider credential, so you were automatically switched to ${nextModel}. Continue the task from where the failed turn left off.`
|
|
45
|
+
: `The previous model (${previousModel}) was unavailable (provider 404 not_found), so you were automatically switched to ${nextModel}. Continue the task from where the failed turn left off.`;
|
|
46
|
+
}
|
|
47
|
+
function legacyCustomTypeKind(customType) {
|
|
48
|
+
switch (customType) {
|
|
49
|
+
case CONTEXT_NUDGE_CUSTOM_TYPE: return 'context-nudge';
|
|
50
|
+
case CONTEXT_INTRO_CUSTOM_TYPE: return 'bearings';
|
|
51
|
+
case REVIEW_BOUNDARY_CUSTOM_TYPE: return 'review-boundary';
|
|
52
|
+
case SITUATIONAL_CONTEXT_CUSTOM_TYPE: return 'situational';
|
|
53
|
+
default: return undefined;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const XML_ATTRIBUTE_NAME = /^[A-Za-z_:][A-Za-z0-9_:.-]*$/;
|
|
57
|
+
const ATTRIBUTE_RE = /\s+([A-Za-z_:][A-Za-z0-9_:.-]*)\s*=\s*(?:"([^"]*)"|'([^']*)')/gy;
|
|
58
|
+
function escapeXmlAttribute(value) {
|
|
59
|
+
return value
|
|
60
|
+
.replaceAll('&', '&')
|
|
61
|
+
.replaceAll('<', '<')
|
|
62
|
+
.replaceAll('>', '>')
|
|
63
|
+
.replaceAll('"', '"')
|
|
64
|
+
.replaceAll("'", ''');
|
|
65
|
+
}
|
|
66
|
+
function escapeXmlText(value) {
|
|
67
|
+
return value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>');
|
|
68
|
+
}
|
|
69
|
+
function unescapeXmlAttribute(value) {
|
|
70
|
+
return value.replace(/&(?:amp|lt|gt|quot|apos);/g, (entity) => ({
|
|
71
|
+
'&': '&',
|
|
72
|
+
'<': '<',
|
|
73
|
+
'>': '>',
|
|
74
|
+
'"': '"',
|
|
75
|
+
''': "'",
|
|
76
|
+
})[entity]);
|
|
77
|
+
}
|
|
78
|
+
function parseAttributes(source) {
|
|
79
|
+
const attributes = {};
|
|
80
|
+
ATTRIBUTE_RE.lastIndex = 0;
|
|
81
|
+
let index = 0;
|
|
82
|
+
while (index < source.length) {
|
|
83
|
+
if (/^\s*$/.test(source.slice(index)))
|
|
84
|
+
break;
|
|
85
|
+
ATTRIBUTE_RE.lastIndex = index;
|
|
86
|
+
const match = ATTRIBUTE_RE.exec(source);
|
|
87
|
+
if (match === null)
|
|
88
|
+
return null;
|
|
89
|
+
const [, key, doubleQuoted, singleQuoted] = match;
|
|
90
|
+
if (key === undefined || Object.hasOwn(attributes, key))
|
|
91
|
+
return null;
|
|
92
|
+
attributes[key] = unescapeXmlAttribute(doubleQuoted ?? singleQuoted ?? '');
|
|
93
|
+
index = ATTRIBUTE_RE.lastIndex;
|
|
94
|
+
}
|
|
95
|
+
return attributes;
|
|
96
|
+
}
|
|
97
|
+
function formatAttributes(attributes) {
|
|
98
|
+
return Object.entries(attributes)
|
|
99
|
+
.filter(([, value]) => value !== undefined)
|
|
100
|
+
.map(([key, value]) => {
|
|
101
|
+
if (!XML_ATTRIBUTE_NAME.test(key))
|
|
102
|
+
throw new Error(`Invalid XML attribute name: ${key}`);
|
|
103
|
+
return ` ${key}="${escapeXmlAttribute(String(value))}"`;
|
|
104
|
+
})
|
|
105
|
+
.join('');
|
|
106
|
+
}
|
|
107
|
+
/** Plain text from a generated message's string or text-block content. */
|
|
108
|
+
export function generatedContextText(message) {
|
|
109
|
+
if (typeof message.content === 'string')
|
|
110
|
+
return message.content;
|
|
111
|
+
if (!Array.isArray(message.content))
|
|
112
|
+
return '';
|
|
113
|
+
return message.content
|
|
114
|
+
.filter((block) => typeof block === 'object'
|
|
115
|
+
&& block !== null
|
|
116
|
+
&& block.type === 'text'
|
|
117
|
+
&& typeof block.text === 'string')
|
|
118
|
+
.map((block) => block.text)
|
|
119
|
+
.join('');
|
|
120
|
+
}
|
|
121
|
+
/** Wrap a user-role runtime message in its whole-message card envelope. */
|
|
122
|
+
export function formatCard(kind, facts, body) {
|
|
123
|
+
if (kind === '')
|
|
124
|
+
throw new Error('Runtime card kind is required');
|
|
125
|
+
if (Object.hasOwn(facts, 'kind'))
|
|
126
|
+
throw new Error('Runtime card facts cannot replace kind');
|
|
127
|
+
return `<runtime${formatAttributes({ kind, ...facts })}>${body}</runtime>`;
|
|
128
|
+
}
|
|
129
|
+
/** Wrap a card whose body is DATA, not markup — the only escape on the API
|
|
130
|
+
* path, so a caller can neither emit a malformed card nor smuggle markup into
|
|
131
|
+
* one. `formatCard` keeps its raw body for the trusted crouter producers that
|
|
132
|
+
* deliberately nest markup (bearings blocks, the inbox `<from>`/`<entry>`
|
|
133
|
+
* grammar `parseInboxBody` depends on). */
|
|
134
|
+
export function formatDataCard(kind, facts, body) {
|
|
135
|
+
return formatCard(kind, facts, escapeXmlText(body));
|
|
136
|
+
}
|
|
137
|
+
function parseInboxBody(body) {
|
|
138
|
+
const senders = [];
|
|
139
|
+
const fromRe = /<from\b([^>]*)>([\s\S]*?)<\/from>/g;
|
|
140
|
+
for (const fromMatch of body.matchAll(fromRe)) {
|
|
141
|
+
const attributes = parseAttributes(fromMatch[1] ?? '');
|
|
142
|
+
if (attributes?.id === undefined)
|
|
143
|
+
continue;
|
|
144
|
+
const entries = [];
|
|
145
|
+
const entryBody = fromMatch[2] ?? '';
|
|
146
|
+
const entryRe = /<entry\b([^>]*)>([\s\S]*?)<\/entry>/g;
|
|
147
|
+
for (const entryMatch of entryBody.matchAll(entryRe)) {
|
|
148
|
+
const entryAttributes = parseAttributes(entryMatch[1] ?? '');
|
|
149
|
+
if (entryAttributes?.kind === undefined)
|
|
150
|
+
continue;
|
|
151
|
+
const disposition = entryAttributes.disposition === 'human-answer' || entryAttributes.disposition === 'human-canceled'
|
|
152
|
+
? entryAttributes.disposition
|
|
153
|
+
: 'report';
|
|
154
|
+
entries.push({
|
|
155
|
+
kind: entryAttributes.kind,
|
|
156
|
+
disposition,
|
|
157
|
+
body: unescapeXmlAttribute(entryMatch[2] ?? ''),
|
|
158
|
+
...(entryAttributes.ref === undefined ? {} : { ref: entryAttributes.ref }),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
// `updates` and `finished` are derived from the entry list; pre-cut
|
|
162
|
+
// scrollback still carries them as attributes, honored when present.
|
|
163
|
+
const legacyUpdates = attributes.updates === undefined ? Number.NaN : Number(attributes.updates);
|
|
164
|
+
senders.push({
|
|
165
|
+
id: attributes.id,
|
|
166
|
+
updates: Number.isFinite(legacyUpdates) ? legacyUpdates : entries.length,
|
|
167
|
+
finished: attributes.finished === 'true' || entries.some((entry) => entry.kind === 'final'),
|
|
168
|
+
entries,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return senders;
|
|
172
|
+
}
|
|
173
|
+
function cardFor(kind, facts, body, senders) {
|
|
174
|
+
return { kind, facts, body, senders };
|
|
175
|
+
}
|
|
176
|
+
/** Parse a whole-message runtime envelope, regardless of delivery role. */
|
|
177
|
+
function parseEnvelope(text) {
|
|
178
|
+
const opening = /^<runtime\b([^>]*)>/.exec(text);
|
|
179
|
+
if (opening === null || !text.endsWith('</runtime>'))
|
|
180
|
+
return null;
|
|
181
|
+
const attributes = parseAttributes(opening[1] ?? '');
|
|
182
|
+
if (attributes?.kind === undefined)
|
|
183
|
+
return null;
|
|
184
|
+
const raw = text.slice(opening[0].length, -'</runtime>'.length);
|
|
185
|
+
const { kind, ...factValues } = attributes;
|
|
186
|
+
const facts = Object.freeze(factValues);
|
|
187
|
+
// A namespaced kind can only have been produced through the API path, whose
|
|
188
|
+
// renderer is `formatDataCard`, so its body is escaped by construction and
|
|
189
|
+
// decodes here. A bare kind's body is raw markup written by a core producer.
|
|
190
|
+
const body = kind.includes(':') ? unescapeXmlAttribute(raw) : raw;
|
|
191
|
+
return cardFor(kind, facts, body, kind === 'inbox' ? parseInboxBody(body) : []);
|
|
192
|
+
}
|
|
193
|
+
/** Parse a runtime envelope, with pre-envelope customType compatibility. */
|
|
194
|
+
export function parseCard(message) {
|
|
195
|
+
const text = generatedContextText(message);
|
|
196
|
+
const envelope = parseEnvelope(text);
|
|
197
|
+
if (envelope !== null)
|
|
198
|
+
return envelope;
|
|
199
|
+
if (message.role !== 'custom')
|
|
200
|
+
return null;
|
|
201
|
+
// Pre-envelope custom-message fallback for old sessions. New custom messages
|
|
202
|
+
// classify through their envelope; customType is delivery metadata only.
|
|
203
|
+
const kind = legacyCustomTypeKind(message.customType);
|
|
204
|
+
return kind === undefined ? null : {
|
|
205
|
+
kind,
|
|
206
|
+
facts: Object.freeze({}),
|
|
207
|
+
body: text,
|
|
208
|
+
senders: [],
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
/** Whether an inbox entry is the person's own words. A `human-answer` entry
|
|
212
|
+
* whose body is itself a runtime card is a runtime notice — review approvals
|
|
213
|
+
* were delivered that way before they became plain prose — never speech. */
|
|
214
|
+
export function isHumanWordsEntry(entry) {
|
|
215
|
+
return entry.disposition === 'human-answer' && parseEnvelope(entry.body.trim()) === null;
|
|
216
|
+
}
|
|
217
|
+
/** Explain how an inbox entry's ref is read without guessing its filesystem location. */
|
|
218
|
+
export function formatInboxRefInstruction(ref) {
|
|
219
|
+
return ref.startsWith('/')
|
|
220
|
+
? `Preview only. Read the full body from file \`${ref}\`.`
|
|
221
|
+
: `Read this report with \`crtr canvas read ${ref}\`.`;
|
|
222
|
+
}
|
|
223
|
+
/** Format a complete inbox card from report bodies resolved by the caller. */
|
|
224
|
+
export function formatInboxCard(sections) {
|
|
225
|
+
const body = sections.map((section) => {
|
|
226
|
+
const entries = section.entries.map((entry) => {
|
|
227
|
+
const disposition = entry.disposition === 'human-answer' || entry.disposition === 'human-canceled'
|
|
228
|
+
? entry.disposition
|
|
229
|
+
: undefined;
|
|
230
|
+
return `<entry${formatAttributes({ kind: entry.kind, ref: entry.ref, disposition })}>${escapeXmlText(entry.body)}</entry>`;
|
|
231
|
+
}).join('\n');
|
|
232
|
+
return `<from${formatAttributes({ id: section.id })}>${entries}</from>`;
|
|
233
|
+
}).join('\n');
|
|
234
|
+
return formatCard('inbox', {}, body);
|
|
235
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare const INLINE_REPORT_MAX_CHARS = 1000;
|
|
2
|
+
/** The rendering fields both inbox projections share. */
|
|
3
|
+
export interface InboxEntryFields {
|
|
4
|
+
ts: string;
|
|
5
|
+
from: string | null;
|
|
6
|
+
from_name?: string;
|
|
7
|
+
label: string;
|
|
8
|
+
ref?: string;
|
|
9
|
+
data?: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
/** A report document's ref (`<node>/reports/<name>`), else undefined. */
|
|
12
|
+
export declare function reportRefNodeId(ref: string | undefined): string | undefined;
|
|
13
|
+
/** Clip a body to the bounded preview stored beside a spilled message body. */
|
|
14
|
+
export declare function clipBody(body: string): {
|
|
15
|
+
text: string;
|
|
16
|
+
clipped: boolean;
|
|
17
|
+
};
|
|
18
|
+
export declare function inlineBody(entry: Pick<InboxEntryFields, 'data' | 'label'>): string;
|
|
19
|
+
/** One entry's card body: an inlined short report, a bounded inline message body, an event
|
|
20
|
+
* push rendered from its metadata, or the label. `report` is the report document's body when
|
|
21
|
+
* the caller could read it. */
|
|
22
|
+
export declare function renderEntryBody(entry: InboxEntryFields, report: string | null): string;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// One inbox entry's card body, shared by the daemon (kickoff digest) and broker (live inbox) renderers.
|
|
2
|
+
import { formatInboxRefInstruction } from './generated-context.js';
|
|
3
|
+
const BODY_MAX_LINES = 12;
|
|
4
|
+
const BODY_MAX_CHARS = 1000;
|
|
5
|
+
export const INLINE_REPORT_MAX_CHARS = 1000;
|
|
6
|
+
/** A report document's ref (`<node>/reports/<name>`), else undefined. */
|
|
7
|
+
export function reportRefNodeId(ref) {
|
|
8
|
+
const match = ref === undefined ? null : /^([a-z0-9-]+)\/reports\/[^/]+$/.exec(ref);
|
|
9
|
+
return match?.[1];
|
|
10
|
+
}
|
|
11
|
+
/** Clip a body to the bounded preview stored beside a spilled message body. */
|
|
12
|
+
export function clipBody(body) {
|
|
13
|
+
let text = body;
|
|
14
|
+
let clipped = false;
|
|
15
|
+
const lines = text.split('\n');
|
|
16
|
+
if (lines.length > BODY_MAX_LINES) {
|
|
17
|
+
text = lines.slice(0, BODY_MAX_LINES).join('\n');
|
|
18
|
+
clipped = true;
|
|
19
|
+
}
|
|
20
|
+
if (text.length > BODY_MAX_CHARS) {
|
|
21
|
+
text = text.slice(0, BODY_MAX_CHARS);
|
|
22
|
+
clipped = true;
|
|
23
|
+
}
|
|
24
|
+
return { text: text.trimEnd(), clipped };
|
|
25
|
+
}
|
|
26
|
+
export function inlineBody(entry) {
|
|
27
|
+
const body = typeof entry.data?.['body'] === 'string' ? String(entry.data['body']).trim() : '';
|
|
28
|
+
return body === '' || body === entry.label ? '' : body;
|
|
29
|
+
}
|
|
30
|
+
function pushOf(entry) {
|
|
31
|
+
const push = entry.data?.['push'];
|
|
32
|
+
return typeof push === 'object' && push !== null && typeof push.event === 'string' ? push : null;
|
|
33
|
+
}
|
|
34
|
+
/** The name a reader resolves the object by: `<owner handle>/<name>`, or the owner's id on an older push. */
|
|
35
|
+
function objectRef(o) {
|
|
36
|
+
const owner = o.owner_handle ?? o.owner;
|
|
37
|
+
return owner === null || owner === undefined ? o.name : `${owner}/${o.name}`;
|
|
38
|
+
}
|
|
39
|
+
function objectText(o) {
|
|
40
|
+
if (o.type === 'document')
|
|
41
|
+
return `[[${objectRef(o)}]]`;
|
|
42
|
+
if (o.type === 'job')
|
|
43
|
+
return `job ${o.id} (\`${o.name}\`)`;
|
|
44
|
+
if (o.type === 'node')
|
|
45
|
+
return `node ${o.name} (${o.id})`;
|
|
46
|
+
return `${o.type} ${o.name}`;
|
|
47
|
+
}
|
|
48
|
+
function actorText(entry, push) {
|
|
49
|
+
if (push.actor === null)
|
|
50
|
+
return 'the runtime';
|
|
51
|
+
return entry.from_name === undefined || entry.from_name === push.actor ? push.actor : `${entry.from_name} (${push.actor})`;
|
|
52
|
+
}
|
|
53
|
+
function list(value) {
|
|
54
|
+
return Array.isArray(value) ? value.filter((v) => typeof v === 'string') : [];
|
|
55
|
+
}
|
|
56
|
+
/** One sentence per event: the object, the actor and the pointer — never the act's content. */
|
|
57
|
+
function renderPush(entry, push) {
|
|
58
|
+
const obj = objectText(push.object);
|
|
59
|
+
const actor = actorText(entry, push);
|
|
60
|
+
const a = push.attrs;
|
|
61
|
+
const readHint = push.object.type === 'document'
|
|
62
|
+
? ` Read it with \`crtr canvas read ${objectRef(push.object)}\`.`
|
|
63
|
+
: '';
|
|
64
|
+
switch (push.event) {
|
|
65
|
+
case 'created':
|
|
66
|
+
return push.object.type === 'node'
|
|
67
|
+
? `${actor} created ${obj}${typeof a['kind'] === 'string' ? `, kind ${a['kind']}` : ''}.`
|
|
68
|
+
: `${actor} created ${obj}.${readHint}`;
|
|
69
|
+
case 'read': return `${actor} read ${obj}.`;
|
|
70
|
+
case 'watched': return `${actor} started watching ${obj}.`;
|
|
71
|
+
case 'deleted': return `${actor} deleted ${obj}.`;
|
|
72
|
+
case 'edited': {
|
|
73
|
+
const authors = list(a['authors']);
|
|
74
|
+
const rationales = list(a['rationales']);
|
|
75
|
+
const range = typeof a['from_revision'] === 'number' && typeof a['revision'] === 'number' && a['revision'] - a['from_revision'] > 1
|
|
76
|
+
? `revisions ${a['from_revision'] + 1}–${a['revision']}` : typeof a['revision'] === 'number' ? `revision ${a['revision']}` : 'a new revision';
|
|
77
|
+
const by = authors.length === 0 ? actor : authors.join(', ');
|
|
78
|
+
const why = rationales.length === 0 ? '' : ` Rationale: ${rationales.join('; ')}.`;
|
|
79
|
+
const merge = a['needs_merge'] === true ? ' Two edits were made from the same revision; read it and edit it to merge them.' : '';
|
|
80
|
+
const unwatchHint = ` You get this because you read or wrote it; if it no longer concerns your work, stop with \`crtr canvas unwatch ${objectRef(push.object)}\`.`;
|
|
81
|
+
return `${by} edited ${obj} (${range}).${why}${merge}${readHint}${unwatchHint}`;
|
|
82
|
+
}
|
|
83
|
+
case 'messaged': return `${actor} messaged ${obj}.`;
|
|
84
|
+
case 'sent': return `${actor} sent a message as ${obj}.`;
|
|
85
|
+
case 'configured': return `${actor} changed the settings of ${obj}${typeof a['changed'] === 'string' ? `: ${a['changed']}` : ''}.`;
|
|
86
|
+
case 'exited': return `${obj[0].toUpperCase()}${obj.slice(1)} exited with code ${String(a['exit-code'])}. Log: ${String(a['log-path'])}`;
|
|
87
|
+
case 'killed': {
|
|
88
|
+
const by = push.actor === null ? '' : ` by ${actor}`;
|
|
89
|
+
const why = typeof a['reason'] === 'string' ? ` (${a['reason']})` : '';
|
|
90
|
+
const claim = a['reason'] === 'deadline'
|
|
91
|
+
? ` It was canceled at its deadline; claim it with \`crtr node bash extend ${push.object.id} --for <duration>\` next time.`
|
|
92
|
+
: '';
|
|
93
|
+
return `${obj[0].toUpperCase()}${obj.slice(1)} was killed${by}${why}.${claim} Log: ${String(a['log-path'])}`;
|
|
94
|
+
}
|
|
95
|
+
default: return entry.label;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/** One entry's card body: an inlined short report, a bounded inline message body, an event
|
|
99
|
+
* push rendered from its metadata, or the label. `report` is the report document's body when
|
|
100
|
+
* the caller could read it. */
|
|
101
|
+
export function renderEntryBody(entry, report) {
|
|
102
|
+
if (report !== null && report.length < INLINE_REPORT_MAX_CHARS && entry.ref !== undefined) {
|
|
103
|
+
return `${report.trimEnd()}\n\n${formatInboxRefInstruction(entry.ref)}`;
|
|
104
|
+
}
|
|
105
|
+
const body = inlineBody(entry);
|
|
106
|
+
if (body === '') {
|
|
107
|
+
const push = pushOf(entry);
|
|
108
|
+
if (push !== null && entry.ref === undefined)
|
|
109
|
+
return renderPush(entry, push);
|
|
110
|
+
return entry.ref === undefined ? entry.label : `${entry.label}\n\n${formatInboxRefInstruction(entry.ref)}`;
|
|
111
|
+
}
|
|
112
|
+
const { text, clipped } = clipBody(body);
|
|
113
|
+
if (entry.ref !== undefined)
|
|
114
|
+
return `${text}\n\n${formatInboxRefInstruction(entry.ref)}`;
|
|
115
|
+
return clipped ? `${text}\n… (body clipped)` : text;
|
|
116
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const DEFAULT_WORKING_GERUND = "working";
|
|
2
|
+
export declare const DEFAULT_WORKING_GERUNDS: readonly ["thinking", "planning", "checking", "working"];
|
|
3
|
+
export declare const DEFAULT_WORKING_ACTIVITY = "Working...";
|
|
4
|
+
/** Keep only usable configured labels; malformed or empty lists fall back to working. */
|
|
5
|
+
export declare function normalizeWorkingGerunds(value: unknown): string[];
|
|
6
|
+
/** Format one configured gerund as the activity label shown by every viewer. */
|
|
7
|
+
export declare function formatWorkingActivity(gerund: unknown): string;
|
|
8
|
+
/** Pick one configured gerund when the broker receives a user message. */
|
|
9
|
+
export declare function randomWorkingActivity(value: unknown, random?: () => number): string;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const DEFAULT_WORKING_GERUND = 'working';
|
|
2
|
+
export const DEFAULT_WORKING_GERUNDS = ['thinking', 'planning', 'checking', DEFAULT_WORKING_GERUND];
|
|
3
|
+
export const DEFAULT_WORKING_ACTIVITY = 'Working...';
|
|
4
|
+
/** Keep only usable configured labels; malformed or empty lists fall back to working. */
|
|
5
|
+
export function normalizeWorkingGerunds(value) {
|
|
6
|
+
if (!Array.isArray(value))
|
|
7
|
+
return [DEFAULT_WORKING_GERUND];
|
|
8
|
+
const gerunds = value
|
|
9
|
+
.filter((entry) => typeof entry === 'string')
|
|
10
|
+
.map((entry) => entry.trim())
|
|
11
|
+
.filter((entry) => entry.length > 0);
|
|
12
|
+
return gerunds.length > 0 ? gerunds : [DEFAULT_WORKING_GERUND];
|
|
13
|
+
}
|
|
14
|
+
/** Format one configured gerund as the activity label shown by every viewer. */
|
|
15
|
+
export function formatWorkingActivity(gerund) {
|
|
16
|
+
const normalized = normalizeWorkingGerunds([gerund])[0] ?? DEFAULT_WORKING_GERUND;
|
|
17
|
+
return `${normalized[0].toUpperCase()}${normalized.slice(1)}...`;
|
|
18
|
+
}
|
|
19
|
+
/** Pick one configured gerund when the broker receives a user message. */
|
|
20
|
+
export function randomWorkingActivity(value, random = Math.random) {
|
|
21
|
+
const gerunds = normalizeWorkingGerunds(value);
|
|
22
|
+
const draw = random();
|
|
23
|
+
const index = Number.isFinite(draw)
|
|
24
|
+
? Math.min(gerunds.length - 1, Math.max(0, Math.floor(draw * gerunds.length)))
|
|
25
|
+
: 0;
|
|
26
|
+
return formatWorkingActivity(gerunds[index]);
|
|
27
|
+
}
|