@intentic/sandbox-contract 1.341.0 → 1.343.0
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/README.md +4 -0
- package/dist/contracts/agent.contract.d.ts +54 -0
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.js +11 -1
- package/dist/contracts/agent.contract.js.map +1 -1
- package/dist/contracts/agents.contract.d.ts +17 -0
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/runner.contract.d.ts +123 -123
- package/dist/contracts/settings.contract.d.ts +284 -6
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.d.ts +33 -30
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/contracts/workspace.contract.d.ts +9 -0
- package/dist/contracts/workspace.contract.d.ts.map +1 -1
- package/dist/contracts/workspace.contract.js +11 -1
- package/dist/contracts/workspace.contract.js.map +1 -1
- package/dist/events/system-events.d.ts +2 -0
- package/dist/events/system-events.d.ts.map +1 -1
- package/dist/index.d.ts +367 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/models/agent-standing.d.ts +1 -0
- package/dist/models/agent-standing.d.ts.map +1 -1
- package/dist/models/agent-standing.js +4 -1
- package/dist/models/agent-standing.js.map +1 -1
- package/dist/protocol/raw/raw-routes.d.ts +4 -0
- package/dist/protocol/raw/raw-routes.d.ts.map +1 -1
- package/dist/protocol/raw/raw-routes.js +1 -0
- package/dist/protocol/raw/raw-routes.js.map +1 -1
- package/dist/schemas/agent.d.ts +4 -0
- package/dist/schemas/agent.d.ts.map +1 -1
- package/dist/schemas/agent.js +4 -0
- package/dist/schemas/agent.js.map +1 -1
- package/dist/schemas/agents-list.d.ts +1 -0
- package/dist/schemas/agents-list.d.ts.map +1 -1
- package/dist/schemas/agents.d.ts +3 -0
- package/dist/schemas/agents.d.ts.map +1 -1
- package/dist/schemas/dev-rebuild-log.d.ts +11 -0
- package/dist/schemas/dev-rebuild-log.d.ts.map +1 -0
- package/dist/schemas/dev-rebuild-log.js +30 -0
- package/dist/schemas/dev-rebuild-log.js.map +1 -0
- package/dist/schemas/devices.d.ts +1 -10
- package/dist/schemas/devices.d.ts.map +1 -1
- package/dist/schemas/devices.js +1 -29
- package/dist/schemas/devices.js.map +1 -1
- package/dist/schemas/keep-warm.d.ts +7 -0
- package/dist/schemas/keep-warm.d.ts.map +1 -1
- package/dist/schemas/keep-warm.js +25 -0
- package/dist/schemas/keep-warm.js.map +1 -1
- package/dist/schemas/providers/plan-limits.d.ts +27 -0
- package/dist/schemas/providers/plan-limits.d.ts.map +1 -1
- package/dist/schemas/providers/plan-limits.js +23 -0
- package/dist/schemas/providers/plan-limits.js.map +1 -1
- package/dist/schemas/providers/usage.d.ts +4 -0
- package/dist/schemas/providers/usage.d.ts.map +1 -1
- package/dist/schemas/providers/usage.js +4 -0
- package/dist/schemas/providers/usage.js.map +1 -1
- package/dist/schemas/settings-experiments.d.ts +15 -0
- package/dist/schemas/settings-experiments.d.ts.map +1 -0
- package/dist/schemas/settings-experiments.js +52 -0
- package/dist/schemas/settings-experiments.js.map +1 -0
- package/dist/schemas/settings.d.ts +293 -13
- package/dist/schemas/settings.d.ts.map +1 -1
- package/dist/schemas/settings.js +13 -40
- package/dist/schemas/settings.js.map +1 -1
- package/dist/schemas/workspace/workspace-tree.d.ts +10 -0
- package/dist/schemas/workspace/workspace-tree.d.ts.map +1 -1
- package/dist/schemas/workspace/workspace-tree.js +15 -0
- package/dist/schemas/workspace/workspace-tree.js.map +1 -1
- package/dist/state/definition.d.ts +40 -12
- package/dist/state/definition.d.ts.map +1 -1
- package/dist/text/preview-guide.d.ts +15 -0
- package/dist/text/preview-guide.d.ts.map +1 -0
- package/dist/text/preview-guide.js +138 -0
- package/dist/text/preview-guide.js.map +1 -0
- package/dist/time/zone.d.ts +1 -0
- package/dist/time/zone.d.ts.map +1 -1
- package/dist/time/zone.js +6 -0
- package/dist/time/zone.js.map +1 -1
- package/package.json +5 -5
- package/src/contracts/agent.contract.ts +13 -0
- package/src/contracts/workspace.contract.ts +15 -0
- package/src/models/agent-standing.ts +21 -3
- package/src/protocol/raw/raw-routes.ts +3 -0
- package/src/schemas/agent.ts +6 -0
- package/src/schemas/dev-rebuild-log.test.ts +1 -1
- package/src/schemas/dev-rebuild-log.ts +58 -0
- package/src/schemas/devices.ts +2 -54
- package/src/schemas/keep-warm.ts +34 -0
- package/src/schemas/providers/plan-limits.ts +29 -0
- package/src/schemas/providers/usage.ts +9 -0
- package/src/schemas/settings-experiments.ts +80 -0
- package/src/schemas/settings.ts +24 -53
- package/src/schemas/workspace/workspace-tree.ts +20 -0
- package/src/text/preview-guide.ts +159 -0
- package/src/time/zone.test.ts +18 -1
- package/src/time/zone.ts +12 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
// What the daemon puts first in a workspace web page it serves for a preview (GET /workspace/preview/{ticket}/*), and
|
|
4
|
+
// what that page may say to the editor framing it. The page runs as it would on a static file server, in a frame
|
|
5
|
+
// sandboxed without `allow-same-origin`, so its origin is opaque: it cannot read the editor, the daemon's API or any
|
|
6
|
+
// storage. This script is what keeps a page written for an ordinary origin working there, and what lets the editor
|
|
7
|
+
// print it, show a Copy button over it, and say which of its files are missing:
|
|
8
|
+
// - `localStorage`, `sessionStorage` and `document.cookie` throw in an opaque origin, and a page that touches them at
|
|
9
|
+
// load stops dead; each is stood in for by one held in memory for the page's life.
|
|
10
|
+
// - A Vite build whose base is an absolute path preloads chunks from the server's root, outside the ticket; its
|
|
11
|
+
// `vite:preloadError` is cancelled, so the import itself, which is relative, goes ahead.
|
|
12
|
+
// - `new Worker(url)` refuses any script an opaque origin did not make, which is every one; a worker is started from a
|
|
13
|
+
// script made here that loads the real one, its own relative imports and fetches resolved against where it lives.
|
|
14
|
+
// - A link to another site opens in a new tab (most refuse to be framed); a link within the page's own files is
|
|
15
|
+
// followed in the frame, as a browser would.
|
|
16
|
+
// - Every file it loads from beside itself is told, so the editor redraws it when one changes, and every one that
|
|
17
|
+
// fails, so the editor can name it.
|
|
18
|
+
// - `intenticPrint` is answered with the page as it stands, addresses made absolute and canvases drawn as pictures.
|
|
19
|
+
export const PREVIEW_GUIDE_SCRIPT = `(() => {
|
|
20
|
+
const say = (message) => parent.postMessage({ intenticHtmlPreview: message }, "*");
|
|
21
|
+
const memory = () => {
|
|
22
|
+
const held = new Map();
|
|
23
|
+
return {
|
|
24
|
+
get length() { return held.size; },
|
|
25
|
+
key: (index) => [...held.keys()][index] ?? null,
|
|
26
|
+
getItem: (key) => (held.has(String(key)) ? held.get(String(key)) : null),
|
|
27
|
+
setItem: (key, value) => { held.set(String(key), String(value)); },
|
|
28
|
+
removeItem: (key) => { held.delete(String(key)); },
|
|
29
|
+
clear: () => { held.clear(); },
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
for (const name of ["localStorage", "sessionStorage"]) {
|
|
33
|
+
try { void window[name]; } catch { Object.defineProperty(window, name, { value: memory(), configurable: true }); }
|
|
34
|
+
}
|
|
35
|
+
try { void document.cookie; } catch {
|
|
36
|
+
const jar = new Map();
|
|
37
|
+
Object.defineProperty(document, "cookie", {
|
|
38
|
+
configurable: true,
|
|
39
|
+
get: () => [...jar].map(([name, value]) => name + "=" + value).join("; "),
|
|
40
|
+
set: (text) => {
|
|
41
|
+
const [pair, ...attributes] = String(text).split(";");
|
|
42
|
+
const at = pair.indexOf("=");
|
|
43
|
+
const name = (at < 0 ? "" : pair.slice(0, at)).trim();
|
|
44
|
+
const value = (at < 0 ? pair : pair.slice(at + 1)).trim();
|
|
45
|
+
const gone = attributes.some((attribute) => {
|
|
46
|
+
const [key, ...rest] = attribute.split("=");
|
|
47
|
+
const setting = rest.join("=").trim();
|
|
48
|
+
const lower = key.trim().toLowerCase();
|
|
49
|
+
return (lower === "max-age" && Number(setting) <= 0) || (lower === "expires" && Date.parse(setting) < Date.now());
|
|
50
|
+
});
|
|
51
|
+
if (gone) jar.delete(name); else jar.set(name, value);
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
addEventListener("vite:preloadError", (event) => event.preventDefault());
|
|
56
|
+
const Native = window.Worker;
|
|
57
|
+
if (typeof Native === "function") {
|
|
58
|
+
window.Worker = function Worker(script, options) {
|
|
59
|
+
const url = new URL(String(script), location.href);
|
|
60
|
+
if (!/^https?:$/.test(url.protocol)) return new Native(script, options);
|
|
61
|
+
const at = JSON.stringify(url.href);
|
|
62
|
+
const fetches = "const get = self.fetch.bind(self); self.fetch = (input, init) => get(typeof input === 'string' ? new URL(input, " + at + ").href : input, init);";
|
|
63
|
+
// Chromium starts no module worker from a blob an opaque origin made, but does from a data address; the
|
|
64
|
+
// fetch fix is a module of its own so it runs before the worker's, as static imports run in order.
|
|
65
|
+
if (options?.type === "module") {
|
|
66
|
+
const data = (code) => "data:text/javascript," + encodeURIComponent(code);
|
|
67
|
+
return new Native(data("import " + JSON.stringify(data(fetches)) + "; import " + at + ";"), options);
|
|
68
|
+
}
|
|
69
|
+
const body = fetches + "const load = self.importScripts.bind(self);" +
|
|
70
|
+
"self.importScripts = (...urls) => load(...urls.map((each) => new URL(each, " + at + ").href)); load(" + at + ");";
|
|
71
|
+
return new Native(URL.createObjectURL(new Blob([body], { type: "text/javascript" })), options);
|
|
72
|
+
};
|
|
73
|
+
window.Worker.prototype = Native.prototype;
|
|
74
|
+
}
|
|
75
|
+
const prefix = location.pathname.split("/").slice(0, 4).join("/") + "/";
|
|
76
|
+
const own = (url) => {
|
|
77
|
+
try {
|
|
78
|
+
const parsed = new URL(url, location.href);
|
|
79
|
+
return parsed.origin === location.origin && parsed.pathname.startsWith(prefix) ? decodeURIComponent(parsed.pathname.slice(prefix.length)) : undefined;
|
|
80
|
+
// allow(silent-catch): an address that does not parse or decode is not one of the page's own files.
|
|
81
|
+
} catch { return undefined; }
|
|
82
|
+
};
|
|
83
|
+
addEventListener("click", (event) => {
|
|
84
|
+
const link = event.target instanceof Element ? event.target.closest("a[href], area[href]") : null;
|
|
85
|
+
if (link === null || event.button !== 0 || event.defaultPrevented) return;
|
|
86
|
+
const href = link.href;
|
|
87
|
+
if (!/^https?:/i.test(href) || own(href) !== undefined) return;
|
|
88
|
+
event.preventDefault();
|
|
89
|
+
say({ href });
|
|
90
|
+
});
|
|
91
|
+
const told = new Set();
|
|
92
|
+
const tell = (urls) => {
|
|
93
|
+
const fresh = [];
|
|
94
|
+
for (const url of urls) {
|
|
95
|
+
const path = own(url);
|
|
96
|
+
if (path !== undefined && !told.has(path)) { told.add(path); fresh.push(path); }
|
|
97
|
+
}
|
|
98
|
+
if (fresh.length > 0) say({ loaded: fresh });
|
|
99
|
+
};
|
|
100
|
+
tell([location.href]);
|
|
101
|
+
new PerformanceObserver((list) => tell(list.getEntries().map((entry) => entry.name))).observe({ type: "resource", buffered: true });
|
|
102
|
+
addEventListener("error", (event) => {
|
|
103
|
+
const element = event.target;
|
|
104
|
+
if (!(element instanceof Element)) return;
|
|
105
|
+
const url = element.currentSrc || element.src || element.href;
|
|
106
|
+
const path = typeof url === "string" ? own(url) : undefined;
|
|
107
|
+
if (path !== undefined) say({ missing: path });
|
|
108
|
+
}, true);
|
|
109
|
+
let moved = 0;
|
|
110
|
+
addEventListener("pointermove", (event) => {
|
|
111
|
+
if (event.pointerType === "touch" || event.timeStamp - moved < 150) return;
|
|
112
|
+
moved = event.timeStamp;
|
|
113
|
+
say({ pointer: true });
|
|
114
|
+
}, true);
|
|
115
|
+
addEventListener("pointerout", (event) => {
|
|
116
|
+
if (event.relatedTarget !== null || event.pointerType === "touch") return;
|
|
117
|
+
moved = 0;
|
|
118
|
+
say({ pointer: false });
|
|
119
|
+
}, true);
|
|
120
|
+
const snapshot = () => {
|
|
121
|
+
const live = [...document.querySelectorAll("canvas")];
|
|
122
|
+
const copy = document.documentElement.cloneNode(true);
|
|
123
|
+
[...copy.querySelectorAll("canvas")].forEach((canvas, index) => {
|
|
124
|
+
try {
|
|
125
|
+
const picture = document.createElement("img");
|
|
126
|
+
picture.src = live[index].toDataURL();
|
|
127
|
+
picture.style.cssText = canvas.style.cssText;
|
|
128
|
+
picture.width = live[index].clientWidth || live[index].width;
|
|
129
|
+
canvas.replaceWith(picture);
|
|
130
|
+
// allow(silent-catch): a canvas drawn from another origin cannot be read, and prints as the page left it.
|
|
131
|
+
} catch {}
|
|
132
|
+
});
|
|
133
|
+
for (const element of copy.querySelectorAll("[src], [href], [poster]")) {
|
|
134
|
+
for (const name of ["src", "href", "poster"]) {
|
|
135
|
+
const value = element.getAttribute(name);
|
|
136
|
+
if (value !== null && !/^(?:data|blob|javascript|mailto):/i.test(value) && URL.canParse(value, document.baseURI)) {
|
|
137
|
+
element.setAttribute(name, new URL(value, document.baseURI).href);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return copy.outerHTML;
|
|
142
|
+
};
|
|
143
|
+
addEventListener("message", (event) => {
|
|
144
|
+
if (event.source === parent && event.data?.intenticPrint === true) parent.postMessage({ intenticPrintHtml: snapshot() }, "*");
|
|
145
|
+
});
|
|
146
|
+
})();`;
|
|
147
|
+
|
|
148
|
+
// What a served page may tell the editor, parsed where it arrives since the page's own scripts can post anything: an
|
|
149
|
+
// internet address to open in a tab, whether a mouse is over it, the files it loaded from beside itself, or one that
|
|
150
|
+
// failed. Paths are relative to the folder the ticket serves (WorkspacePreviewTicketSchema `root`).
|
|
151
|
+
export const PreviewGuideSayingSchema = z.object({
|
|
152
|
+
intenticHtmlPreview: z.union([
|
|
153
|
+
z.object({ href: z.string().regex(/^https?:\/\//i) }),
|
|
154
|
+
z.object({ pointer: z.boolean() }),
|
|
155
|
+
z.object({ loaded: z.array(z.string()).max(1000) }),
|
|
156
|
+
z.object({ missing: z.string() }),
|
|
157
|
+
]),
|
|
158
|
+
});
|
|
159
|
+
export type PreviewGuideSaying = z.infer<typeof PreviewGuideSayingSchema>["intenticHtmlPreview"];
|
package/src/time/zone.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { asCivilDay, asZone, civilDayIn, cronOptions, isZone, sameClock, utcDayOf, UTC, zoneLabel } from "./zone.js";
|
|
1
|
+
import { asCivilDay, asZone, civilDayIn, cronOptions, isZone, sameClock, utcDayOf, UTC, wallClockInstant, zoneLabel } from "./zone.js";
|
|
2
2
|
import type { Zone } from "./zone.js";
|
|
3
3
|
|
|
4
4
|
// The distinctions this module exists to keep, each of which was a live bug before it: a cron evaluated in the
|
|
@@ -87,3 +87,20 @@ describe(`cronOptions`, () => {
|
|
|
87
87
|
expect(cronOptions(WARSAW)).toEqual({ timezone: `Europe/Warsaw` });
|
|
88
88
|
});
|
|
89
89
|
});
|
|
90
|
+
|
|
91
|
+
/* A time of day a program on some clock wrote ("try again at 5:46 PM") is an instant only with that clock's zone. */
|
|
92
|
+
describe(`wallClockInstant`, () => {
|
|
93
|
+
test(`reads the same wall time as a different instant on each clock`, () => {
|
|
94
|
+
expect(wallClockInstant(asCivilDay(`2026-09-20`)!, 22, 43, UTC)).toBe(Date.UTC(2026, 8, 20, 22, 43));
|
|
95
|
+
expect(wallClockInstant(asCivilDay(`2026-09-20`)!, 22, 43, WARSAW)).toBe(SEPTEMBER);
|
|
96
|
+
expect(wallClockInstant(asCivilDay(`2026-12-20`)!, 15, 43, NEW_YORK)).toBe(DECEMBER);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Warsaw changes its clocks at 01:00 UTC: on 2026-03-29 from +1 to +2, on 2026-10-25 from +2 to +1. An hour read just
|
|
100
|
+
// before the change sits on the other side of it from its naive guess, whose offset alone would be an hour out.
|
|
101
|
+
test(`takes the offset in force at the time itself, not at the naive guess, across a changeover`, () => {
|
|
102
|
+
expect(wallClockInstant(asCivilDay(`2026-03-29`)!, 1, 30, WARSAW)).toBe(Date.UTC(2026, 2, 29, 0, 30));
|
|
103
|
+
expect(wallClockInstant(asCivilDay(`2026-10-25`)!, 1, 30, WARSAW)).toBe(Date.UTC(2026, 9, 24, 23, 30));
|
|
104
|
+
expect(wallClockInstant(asCivilDay(`2026-10-25`)!, 9, 0, WARSAW)).toBe(Date.UTC(2026, 9, 25, 8, 0));
|
|
105
|
+
});
|
|
106
|
+
});
|
package/src/time/zone.ts
CHANGED
|
@@ -115,6 +115,18 @@ export const nextDayStartIn = (at: number, zone: Zone): number => {
|
|
|
115
115
|
return midnightUtc - offsetMinutes(midnightUtc, zone) * 60_000;
|
|
116
116
|
};
|
|
117
117
|
|
|
118
|
+
/**
|
|
119
|
+
* The instant a named clock reads `hours:minutes` on a calendar day: what a sentence like "try again at 5:46 PM" means,
|
|
120
|
+
* written by a program on that clock. The offset is taken again at the first answer, since across a summer-time
|
|
121
|
+
* changeover the offset at the naive guess is not the one in force at the time itself.
|
|
122
|
+
*/
|
|
123
|
+
export const wallClockInstant = (day: CivilDay, hours: number, minutes: number, zone: Zone): number => {
|
|
124
|
+
const [year = 0, month = 1, date = 1] = day.split("-").map(Number);
|
|
125
|
+
const naive = Date.UTC(year, month - 1, date, hours, minutes);
|
|
126
|
+
const guess = naive - offsetMinutes(naive, zone) * 60_000;
|
|
127
|
+
return naive - offsetMinutes(guess, zone) * 60_000;
|
|
128
|
+
};
|
|
129
|
+
|
|
118
130
|
/** Whether two zones are showing the same wall clock right now — which is when naming one on screen would be noise. */
|
|
119
131
|
export const sameClock = (a: Zone, b: Zone, at: number = Date.now()): boolean => a === b || offsetMinutes(at, a) === offsetMinutes(at, b);
|
|
120
132
|
|