@12ui/design 0.2.76 → 0.2.78
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/SKILL.md +1 -1
- package/dist/cli-codex-readiness.d.ts +6 -0
- package/dist/cli-codex-readiness.d.ts.map +1 -1
- package/dist/cli-codex-readiness.js +12 -12
- package/dist/cli-codex-readiness.js.map +1 -1
- package/dist/cli-help/design.d.ts +2 -2
- package/dist/cli-help/design.d.ts.map +1 -1
- package/dist/cli-help/design.js +2 -0
- package/dist/cli-help/design.js.map +1 -1
- package/dist/cli-help/index.d.ts +2 -2
- package/dist/cli-review-command.d.ts.map +1 -1
- package/dist/cli-review-command.js +2 -0
- package/dist/cli-review-command.js.map +1 -1
- package/dist/cli-run.d.ts.map +1 -1
- package/dist/cli-run.js +6 -0
- package/dist/cli-run.js.map +1 -1
- package/dist/cli-workspace-draw.d.ts +1 -11
- package/dist/cli-workspace-draw.d.ts.map +1 -1
- package/dist/cli-workspace-draw.js +18 -18
- package/dist/cli-workspace-draw.js.map +1 -1
- package/dist/cli-workspace-runner.d.ts +30 -0
- package/dist/cli-workspace-runner.d.ts.map +1 -0
- package/dist/cli-workspace-runner.js +105 -0
- package/dist/cli-workspace-runner.js.map +1 -0
- package/dist/mcp-slate-image.js +1 -1
- package/dist/mcp-slate-image.js.map +1 -1
- package/dist/mcp-slate-tool-defs.js +1 -1
- package/dist/mcp-slate-tool-defs.js.map +1 -1
- package/dist/mcp-slate-ui-copy.d.ts +403 -33
- package/dist/mcp-slate-ui-copy.d.ts.map +1 -1
- package/dist/mcp-slate-ui-copy.js +247 -57
- package/dist/mcp-slate-ui-copy.js.map +1 -1
- package/dist/mcp-slate-ui-icu.d.ts +47 -0
- package/dist/mcp-slate-ui-icu.d.ts.map +1 -0
- package/dist/mcp-slate-ui-icu.js +229 -0
- package/dist/mcp-slate-ui-icu.js.map +1 -0
- package/dist/mcp-slate-ui-locale-match.d.ts +12 -0
- package/dist/mcp-slate-ui-locale-match.d.ts.map +1 -0
- package/dist/mcp-slate-ui-locale-match.js +34 -0
- package/dist/mcp-slate-ui-locale-match.js.map +1 -0
- package/dist/mcp-slate-ui-locales/de-DE.d.ts +9 -0
- package/dist/mcp-slate-ui-locales/de-DE.d.ts.map +1 -0
- package/dist/mcp-slate-ui-locales/de-DE.js +217 -0
- package/dist/mcp-slate-ui-locales/de-DE.js.map +1 -0
- package/dist/mcp-slate-ui-locales/es-419.d.ts +9 -0
- package/dist/mcp-slate-ui-locales/es-419.d.ts.map +1 -0
- package/dist/mcp-slate-ui-locales/es-419.js +217 -0
- package/dist/mcp-slate-ui-locales/es-419.js.map +1 -0
- package/dist/mcp-slate-ui-locales/es-ES.d.ts +9 -0
- package/dist/mcp-slate-ui-locales/es-ES.d.ts.map +1 -0
- package/dist/mcp-slate-ui-locales/es-ES.js +217 -0
- package/dist/mcp-slate-ui-locales/es-ES.js.map +1 -0
- package/dist/mcp-slate-ui-locales/fr-FR.d.ts +9 -0
- package/dist/mcp-slate-ui-locales/fr-FR.d.ts.map +1 -0
- package/dist/mcp-slate-ui-locales/fr-FR.js +217 -0
- package/dist/mcp-slate-ui-locales/fr-FR.js.map +1 -0
- package/dist/mcp-slate-ui-locales/hi-IN.d.ts +9 -0
- package/dist/mcp-slate-ui-locales/hi-IN.d.ts.map +1 -0
- package/dist/mcp-slate-ui-locales/hi-IN.js +217 -0
- package/dist/mcp-slate-ui-locales/hi-IN.js.map +1 -0
- package/dist/mcp-slate-ui-locales/index.d.ts +11 -0
- package/dist/mcp-slate-ui-locales/index.d.ts.map +1 -0
- package/dist/mcp-slate-ui-locales/index.js +34 -0
- package/dist/mcp-slate-ui-locales/index.js.map +1 -0
- package/dist/mcp-slate-ui-locales/it-IT.d.ts +9 -0
- package/dist/mcp-slate-ui-locales/it-IT.d.ts.map +1 -0
- package/dist/mcp-slate-ui-locales/it-IT.js +217 -0
- package/dist/mcp-slate-ui-locales/it-IT.js.map +1 -0
- package/dist/mcp-slate-ui-locales/ja-JP.d.ts +9 -0
- package/dist/mcp-slate-ui-locales/ja-JP.d.ts.map +1 -0
- package/dist/mcp-slate-ui-locales/ja-JP.js +217 -0
- package/dist/mcp-slate-ui-locales/ja-JP.js.map +1 -0
- package/dist/mcp-slate-ui-locales/ko-KR.d.ts +9 -0
- package/dist/mcp-slate-ui-locales/ko-KR.d.ts.map +1 -0
- package/dist/mcp-slate-ui-locales/ko-KR.js +217 -0
- package/dist/mcp-slate-ui-locales/ko-KR.js.map +1 -0
- package/dist/mcp-slate-ui-locales/pt-BR.d.ts +9 -0
- package/dist/mcp-slate-ui-locales/pt-BR.d.ts.map +1 -0
- package/dist/mcp-slate-ui-locales/pt-BR.js +217 -0
- package/dist/mcp-slate-ui-locales/pt-BR.js.map +1 -0
- package/dist/mcp-slate-ui-locales/zh-CN.d.ts +9 -0
- package/dist/mcp-slate-ui-locales/zh-CN.d.ts.map +1 -0
- package/dist/mcp-slate-ui-locales/zh-CN.js +217 -0
- package/dist/mcp-slate-ui-locales/zh-CN.js.map +1 -0
- package/dist/mcp-slate-ui-locales/zh-TW.d.ts +9 -0
- package/dist/mcp-slate-ui-locales/zh-TW.d.ts.map +1 -0
- package/dist/mcp-slate-ui-locales/zh-TW.js +217 -0
- package/dist/mcp-slate-ui-locales/zh-TW.js.map +1 -0
- package/dist/mcp-slate-ui-script-bridge.d.ts.map +1 -1
- package/dist/mcp-slate-ui-script-bridge.js +8 -9
- package/dist/mcp-slate-ui-script-bridge.js.map +1 -1
- package/dist/mcp-slate-ui-script-dialog.d.ts.map +1 -1
- package/dist/mcp-slate-ui-script-dialog.js +60 -30
- package/dist/mcp-slate-ui-script-dialog.js.map +1 -1
- package/dist/mcp-slate-ui-script-inline.d.ts +9 -6
- package/dist/mcp-slate-ui-script-inline.d.ts.map +1 -1
- package/dist/mcp-slate-ui-script-inline.js +91 -46
- package/dist/mcp-slate-ui-script-inline.js.map +1 -1
- package/dist/mcp-slate-ui-script-inspiration-collage.d.ts +20 -0
- package/dist/mcp-slate-ui-script-inspiration-collage.d.ts.map +1 -0
- package/dist/mcp-slate-ui-script-inspiration-collage.js +68 -0
- package/dist/mcp-slate-ui-script-inspiration-collage.js.map +1 -0
- package/dist/mcp-slate-ui-script-inspiration-grid.d.ts +26 -0
- package/dist/mcp-slate-ui-script-inspiration-grid.d.ts.map +1 -0
- package/dist/mcp-slate-ui-script-inspiration-grid.js +246 -0
- package/dist/mcp-slate-ui-script-inspiration-grid.js.map +1 -0
- package/dist/mcp-slate-ui-script-inspiration.d.ts +10 -9
- package/dist/mcp-slate-ui-script-inspiration.d.ts.map +1 -1
- package/dist/mcp-slate-ui-script-inspiration.js +161 -120
- package/dist/mcp-slate-ui-script-inspiration.js.map +1 -1
- package/dist/mcp-slate-ui-script-l10n.d.ts +29 -0
- package/dist/mcp-slate-ui-script-l10n.d.ts.map +1 -0
- package/dist/mcp-slate-ui-script-l10n.js +135 -0
- package/dist/mcp-slate-ui-script-l10n.js.map +1 -0
- package/dist/mcp-slate-ui-script-more.d.ts +3 -2
- package/dist/mcp-slate-ui-script-more.d.ts.map +1 -1
- package/dist/mcp-slate-ui-script-more.js +19 -18
- package/dist/mcp-slate-ui-script-more.js.map +1 -1
- package/dist/mcp-slate-ui-script-pause.d.ts +33 -0
- package/dist/mcp-slate-ui-script-pause.d.ts.map +1 -0
- package/dist/mcp-slate-ui-script-pause.js +217 -0
- package/dist/mcp-slate-ui-script-pause.js.map +1 -0
- package/dist/mcp-slate-ui-script-remote.d.ts +2 -0
- package/dist/mcp-slate-ui-script-remote.d.ts.map +1 -1
- package/dist/mcp-slate-ui-script-remote.js +47 -6
- package/dist/mcp-slate-ui-script-remote.js.map +1 -1
- package/dist/mcp-slate-ui-script-scribble.js +1 -1
- package/dist/mcp-slate-ui-script-select.js +6 -6
- package/dist/mcp-slate-ui-script-stage.d.ts +3 -2
- package/dist/mcp-slate-ui-script-stage.d.ts.map +1 -1
- package/dist/mcp-slate-ui-script-stage.js +31 -19
- package/dist/mcp-slate-ui-script-stage.js.map +1 -1
- package/dist/mcp-slate-ui-script-state.d.ts.map +1 -1
- package/dist/mcp-slate-ui-script-state.js +77 -57
- package/dist/mcp-slate-ui-script-state.js.map +1 -1
- package/dist/mcp-slate-ui-script.d.ts +2 -2
- package/dist/mcp-slate-ui-script.d.ts.map +1 -1
- package/dist/mcp-slate-ui-script.js +21 -7
- package/dist/mcp-slate-ui-script.js.map +1 -1
- package/dist/mcp-slate-ui-styles-dialog.d.ts +6 -7
- package/dist/mcp-slate-ui-styles-dialog.d.ts.map +1 -1
- package/dist/mcp-slate-ui-styles-dialog.js +13 -29
- package/dist/mcp-slate-ui-styles-dialog.js.map +1 -1
- package/dist/mcp-slate-ui-styles-inspiration.d.ts +18 -0
- package/dist/mcp-slate-ui-styles-inspiration.d.ts.map +1 -0
- package/dist/mcp-slate-ui-styles-inspiration.js +43 -0
- package/dist/mcp-slate-ui-styles-inspiration.js.map +1 -0
- package/dist/mcp-slate-ui-styles-pause.d.ts +10 -0
- package/dist/mcp-slate-ui-styles-pause.d.ts.map +1 -0
- package/dist/mcp-slate-ui-styles-pause.js +25 -0
- package/dist/mcp-slate-ui-styles-pause.js.map +1 -0
- package/dist/mcp-slate-ui-styles-stage.d.ts +10 -6
- package/dist/mcp-slate-ui-styles-stage.d.ts.map +1 -1
- package/dist/mcp-slate-ui-styles-stage.js +24 -16
- package/dist/mcp-slate-ui-styles-stage.js.map +1 -1
- package/dist/mcp-slate-ui-styles.d.ts +12 -5
- package/dist/mcp-slate-ui-styles.d.ts.map +1 -1
- package/dist/mcp-slate-ui-styles.js +38 -21
- package/dist/mcp-slate-ui-styles.js.map +1 -1
- package/dist/mcp-slate-ui.d.ts +11 -3
- package/dist/mcp-slate-ui.d.ts.map +1 -1
- package/dist/mcp-slate-ui.js +24 -16
- package/dist/mcp-slate-ui.js.map +1 -1
- package/dist/mosaic-bands.d.ts +44 -0
- package/dist/mosaic-bands.d.ts.map +1 -0
- package/dist/mosaic-bands.js +153 -0
- package/dist/mosaic-bands.js.map +1 -0
- package/dist/review-discovery.d.ts.map +1 -1
- package/dist/review-discovery.js +36 -54
- package/dist/review-discovery.js.map +1 -1
- package/dist/review-page.d.ts.map +1 -1
- package/dist/review-page.js +2 -1
- package/dist/review-page.js.map +1 -1
- package/dist/review-run-roots.d.ts +40 -0
- package/dist/review-run-roots.d.ts.map +1 -0
- package/dist/review-run-roots.js +94 -0
- package/dist/review-run-roots.js.map +1 -0
- package/dist/review-server.d.ts +2 -0
- package/dist/review-server.d.ts.map +1 -1
- package/dist/review-server.js +2 -0
- package/dist/review-server.js.map +1 -1
- package/dist/review-types.d.ts +10 -0
- package/dist/review-types.d.ts.map +1 -1
- package/dist/slate-actions.d.ts +3 -3
- package/dist/slate-actions.d.ts.map +1 -1
- package/dist/slate-actions.js +9 -8
- package/dist/slate-actions.js.map +1 -1
- package/dist/slate-browser-host-script.d.ts +4 -0
- package/dist/slate-browser-host-script.d.ts.map +1 -1
- package/dist/slate-browser-host-script.js +25 -6
- package/dist/slate-browser-host-script.js.map +1 -1
- package/dist/slate-browser-host.d.ts +2 -1
- package/dist/slate-browser-host.d.ts.map +1 -1
- package/dist/slate-browser-host.js +18 -5
- package/dist/slate-browser-host.js.map +1 -1
- package/dist/slate-commit.js +7 -7
- package/dist/slate-commit.js.map +1 -1
- package/dist/slate-corpus.d.ts.map +1 -1
- package/dist/slate-corpus.js +3 -2
- package/dist/slate-corpus.js.map +1 -1
- package/dist/slate-edit-runner.d.ts.map +1 -1
- package/dist/slate-edit-runner.js +5 -4
- package/dist/slate-edit-runner.js.map +1 -1
- package/dist/slate-editability.d.ts +2 -0
- package/dist/slate-editability.d.ts.map +1 -1
- package/dist/slate-editability.js +1 -1
- package/dist/slate-editability.js.map +1 -1
- package/dist/slate-errors.d.ts +7 -1
- package/dist/slate-errors.d.ts.map +1 -1
- package/dist/slate-errors.js +3 -2
- package/dist/slate-errors.js.map +1 -1
- package/dist/slate-handoff-options.d.ts +2 -1
- package/dist/slate-handoff-options.d.ts.map +1 -1
- package/dist/slate-handoff-options.js +2 -2
- package/dist/slate-handoff-options.js.map +1 -1
- package/dist/slate-handoff.js +1 -1
- package/dist/slate-handoff.js.map +1 -1
- package/dist/slate-image-executor.d.ts +28 -3
- package/dist/slate-image-executor.d.ts.map +1 -1
- package/dist/slate-image-executor.js +55 -11
- package/dist/slate-image-executor.js.map +1 -1
- package/dist/slate-inspiration.js +2 -2
- package/dist/slate-inspiration.js.map +1 -1
- package/dist/slate-local-image.d.ts.map +1 -1
- package/dist/slate-local-image.js +4 -1
- package/dist/slate-local-image.js.map +1 -1
- package/dist/slate-lock.js +1 -1
- package/dist/slate-lock.js.map +1 -1
- package/dist/slate-resume.js +6 -6
- package/dist/slate-resume.js.map +1 -1
- package/dist/slate-round-params.d.ts +3 -0
- package/dist/slate-round-params.d.ts.map +1 -1
- package/dist/slate-round-params.js +5 -0
- package/dist/slate-round-params.js.map +1 -1
- package/dist/slate-round-prompt.d.ts +15 -3
- package/dist/slate-round-prompt.d.ts.map +1 -1
- package/dist/slate-round-prompt.js +14 -5
- package/dist/slate-round-prompt.js.map +1 -1
- package/dist/slate-round-queries.d.ts +18 -0
- package/dist/slate-round-queries.d.ts.map +1 -0
- package/dist/slate-round-queries.js +29 -0
- package/dist/slate-round-queries.js.map +1 -0
- package/dist/slate-round-runner.d.ts.map +1 -1
- package/dist/slate-round-runner.js +12 -9
- package/dist/slate-round-runner.js.map +1 -1
- package/dist/slate-round-search.js +5 -5
- package/dist/slate-round-search.js.map +1 -1
- package/dist/slate-run-action.d.ts.map +1 -1
- package/dist/slate-run-action.js +2 -1
- package/dist/slate-run-action.js.map +1 -1
- package/dist/slate-runner-context.d.ts +3 -1
- package/dist/slate-runner-context.d.ts.map +1 -1
- package/dist/slate-runner-context.js +4 -3
- package/dist/slate-runner-context.js.map +1 -1
- package/dist/slate-runner-failure.d.ts +7 -1
- package/dist/slate-runner-failure.d.ts.map +1 -1
- package/dist/slate-runner-failure.js +6 -5
- package/dist/slate-runner-failure.js.map +1 -1
- package/dist/slate-runtime.d.ts.map +1 -1
- package/dist/slate-runtime.js +1 -0
- package/dist/slate-runtime.js.map +1 -1
- package/dist/slate-versions.js +3 -3
- package/dist/slate-versions.js.map +1 -1
- package/dist/slate-view-contract.d.ts +62 -1
- package/dist/slate-view-contract.d.ts.map +1 -1
- package/dist/slate-view-contract.js +30 -0
- package/dist/slate-view-contract.js.map +1 -1
- package/dist/slate-view.d.ts.map +1 -1
- package/dist/slate-view.js +19 -3
- package/dist/slate-view.js.map +1 -1
- package/dist/slate-workspace-parse.d.ts.map +1 -1
- package/dist/slate-workspace-parse.js +22 -0
- package/dist/slate-workspace-parse.js.map +1 -1
- package/dist/slate-workspace-types.d.ts +3 -1
- package/dist/slate-workspace-types.d.ts.map +1 -1
- package/dist/workspace-draw-client.d.ts +58 -5
- package/dist/workspace-draw-client.d.ts.map +1 -1
- package/dist/workspace-draw-client.js +82 -9
- package/dist/workspace-draw-client.js.map +1 -1
- package/dist/workspace-draw-codex.d.ts +9 -4
- package/dist/workspace-draw-codex.d.ts.map +1 -1
- package/dist/workspace-draw-codex.js +17 -3
- package/dist/workspace-draw-codex.js.map +1 -1
- package/dist/workspace-draw-messages.d.ts +31 -0
- package/dist/workspace-draw-messages.d.ts.map +1 -1
- package/dist/workspace-draw-messages.js +30 -0
- package/dist/workspace-draw-messages.js.map +1 -1
- package/dist/workspace-draw-run.d.ts +19 -1
- package/dist/workspace-draw-run.d.ts.map +1 -1
- package/dist/workspace-draw-run.js +98 -5
- package/dist/workspace-draw-run.js.map +1 -1
- package/dist/workspace-draw-store.d.ts +2 -0
- package/dist/workspace-draw-store.d.ts.map +1 -1
- package/dist/workspace-draw-store.js +3 -1
- package/dist/workspace-draw-store.js.map +1 -1
- package/dist/workspace-draw-types.d.ts +2 -0
- package/dist/workspace-draw-types.d.ts.map +1 -1
- package/dist/workspace-draw-types.js +2 -0
- package/dist/workspace-draw-types.js.map +1 -1
- package/dist/workspace-origin.d.ts +15 -0
- package/dist/workspace-origin.d.ts.map +1 -0
- package/dist/workspace-origin.js +36 -0
- package/dist/workspace-origin.js.map +1 -0
- package/dist/workspace-runner-children.d.ts +39 -0
- package/dist/workspace-runner-children.d.ts.map +1 -0
- package/dist/workspace-runner-children.js +139 -0
- package/dist/workspace-runner-children.js.map +1 -0
- package/dist/workspace-runner-client.d.ts +43 -0
- package/dist/workspace-runner-client.d.ts.map +1 -0
- package/dist/workspace-runner-client.js +72 -0
- package/dist/workspace-runner-client.js.map +1 -0
- package/dist/workspace-runner-main.d.ts +56 -0
- package/dist/workspace-runner-main.d.ts.map +1 -0
- package/dist/workspace-runner-main.js +378 -0
- package/dist/workspace-runner-main.js.map +1 -0
- package/dist/workspace-runner-policy.d.ts +40 -0
- package/dist/workspace-runner-policy.d.ts.map +1 -0
- package/dist/workspace-runner-policy.js +47 -0
- package/dist/workspace-runner-policy.js.map +1 -0
- package/dist/workspace-runner-spawn.d.ts +42 -0
- package/dist/workspace-runner-spawn.d.ts.map +1 -0
- package/dist/workspace-runner-spawn.js +60 -0
- package/dist/workspace-runner-spawn.js.map +1 -0
- package/dist/workspace-runner-stop.d.ts +25 -0
- package/dist/workspace-runner-stop.d.ts.map +1 -0
- package/dist/workspace-runner-stop.js +44 -0
- package/dist/workspace-runner-stop.js.map +1 -0
- package/dist/workspace-runner-store.d.ts +26 -0
- package/dist/workspace-runner-store.d.ts.map +1 -0
- package/dist/workspace-runner-store.js +114 -0
- package/dist/workspace-runner-store.js.map +1 -0
- package/open-design.json +2 -2
- package/package.json +1 -1
- package/runtime/codex-conversion/src/images/native-frame.mjs +103 -0
- package/runtime/codex-conversion/src/images/native-invoke.mjs +24 -5
- package/runtime/codex-conversion/src/images/native.mjs +14 -5
- package/runtime/codex-conversion/src/run-store.mjs +21 -1
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `12ui workspace runner stop`: the person's way to end the workspace runners on this Mac. Each live runner gets
|
|
3
|
+
* SIGTERM (it claims nothing more, finishes the drawing in hand, says goodbye, deletes its token and exits); a folder
|
|
4
|
+
* whose runner is gone has its leftover token deleted; and every Codex call a killed runner left running is stopped.
|
|
5
|
+
*/
|
|
6
|
+
export type StopScope = {
|
|
7
|
+
all: true;
|
|
8
|
+
} | {
|
|
9
|
+
all: false;
|
|
10
|
+
origin: string;
|
|
11
|
+
workspace: string | null;
|
|
12
|
+
};
|
|
13
|
+
export type StopReport = {
|
|
14
|
+
stopped: Array<{
|
|
15
|
+
workspace: string;
|
|
16
|
+
pid: number;
|
|
17
|
+
}>;
|
|
18
|
+
codexStopped: number[];
|
|
19
|
+
};
|
|
20
|
+
export declare const stopWorkspaceRunners: (args: {
|
|
21
|
+
dataRoot: string;
|
|
22
|
+
scope: StopScope;
|
|
23
|
+
}) => Promise<StopReport>;
|
|
24
|
+
export declare const stopMessage: (report: StopReport) => string;
|
|
25
|
+
//# sourceMappingURL=workspace-runner-stop.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-runner-stop.d.ts","sourceRoot":"","sources":["../src/workspace-runner-stop.ts"],"names":[],"mappings":"AAKA;;;;GAIG;AAEH,MAAM,MAAM,SAAS,GAAG;IAAE,GAAG,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,GAAG,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAEjG,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnD,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB,CAAC;AASF,eAAO,MAAM,oBAAoB,GAAU,MAAM;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,SAAS,CAAA;CAAE,KAAG,OAAO,CAAC,UAAU,CAmB3G,CAAC;AAIF,eAAO,MAAM,WAAW,GAAI,QAAQ,UAAU,KAAG,MAKhD,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { reapCodexChildren } from './workspace-runner-children.js';
|
|
3
|
+
import { listRunnerDirs, liveRunnerHolder, removeRunnerToken, runnerDirOf } from './workspace-runner-store.js';
|
|
4
|
+
const folders = async (dataRoot, scope) => {
|
|
5
|
+
if (scope.all)
|
|
6
|
+
return await listRunnerDirs(dataRoot, null);
|
|
7
|
+
if (scope.workspace === null)
|
|
8
|
+
return await listRunnerDirs(dataRoot, scope.origin);
|
|
9
|
+
const dir = runnerDirOf(dataRoot, scope.origin, scope.workspace);
|
|
10
|
+
return existsSync(dir) ? [{ dir, workspace: scope.workspace }] : [];
|
|
11
|
+
};
|
|
12
|
+
export const stopWorkspaceRunners = async (args) => {
|
|
13
|
+
const report = { stopped: [], codexStopped: [] };
|
|
14
|
+
for (const { dir, workspace } of await folders(args.dataRoot, args.scope)) {
|
|
15
|
+
const holder = await liveRunnerHolder(dir);
|
|
16
|
+
if (holder) {
|
|
17
|
+
try {
|
|
18
|
+
process.kill(holder.pid, 'SIGTERM');
|
|
19
|
+
report.stopped.push({ workspace, pid: holder.pid });
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
if (error.code !== 'ESRCH')
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
await removeRunnerToken(dir);
|
|
28
|
+
}
|
|
29
|
+
// A live runner finishes its own calls; a call whose runner is gone was left behind by a killed one.
|
|
30
|
+
const reaped = await reapCodexChildren(dir);
|
|
31
|
+
report.codexStopped.push(...reaped.filter((entry) => entry.outcome === 'stopped').map((entry) => entry.pgid));
|
|
32
|
+
}
|
|
33
|
+
return report;
|
|
34
|
+
};
|
|
35
|
+
const plural = (count, word) => `${count} ${word}${count === 1 ? '' : 's'}`;
|
|
36
|
+
export const stopMessage = (report) => {
|
|
37
|
+
const parts = [];
|
|
38
|
+
if (report.stopped.length > 0)
|
|
39
|
+
parts.push(`Stopped ${plural(report.stopped.length, 'workspace runner')}; each finishes the drawing in hand, then exits.`);
|
|
40
|
+
if (report.codexStopped.length > 0)
|
|
41
|
+
parts.push(`Stopped ${plural(report.codexStopped.length, 'Codex call')} a killed runner left behind.`);
|
|
42
|
+
return parts.length > 0 ? parts.join(' ') : 'No workspace runner is running on this Mac.';
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=workspace-runner-stop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-runner-stop.js","sourceRoot":"","sources":["../src/workspace-runner-stop.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type LockHolder } from './slate-lock.js';
|
|
2
|
+
/** A workspace's public key as the plan names it (`wk_` and 8 hex on design.12ui.com): path-safe by construction. */
|
|
3
|
+
export declare const WORKSPACE_KEY_PATTERN: RegExp;
|
|
4
|
+
/** The origin is part of the key, so a local proof server and design.12ui.com never share a runner. */
|
|
5
|
+
export declare const runnerDirOf: (dataRoot: string, origin: string, workspace: string) => string;
|
|
6
|
+
/** Every runner folder on this Mac, or only those of one origin: `{dir, workspace}`. */
|
|
7
|
+
export declare const listRunnerDirs: (dataRoot: string, origin: string | null) => Promise<Array<{
|
|
8
|
+
dir: string;
|
|
9
|
+
workspace: string;
|
|
10
|
+
}>>;
|
|
11
|
+
export declare const runnerLockFile: (dir: string) => string;
|
|
12
|
+
export declare const ensureRunnerDir: (dir: string) => Promise<void>;
|
|
13
|
+
/** Replace the token atomically (0600): a reader sees the old token or the new one, never half of either. */
|
|
14
|
+
export declare const writeRunnerToken: (dir: string, token: string) => Promise<void>;
|
|
15
|
+
/** The stored token, or null when it is missing or not a token. */
|
|
16
|
+
export declare const readRunnerToken: (dir: string) => Promise<string | null>;
|
|
17
|
+
/** When the stored token was written (its mtime), or null when there is none. */
|
|
18
|
+
export declare const runnerTokenWrittenAtMs: (dir: string) => Promise<number | null>;
|
|
19
|
+
/** The runner deletes the token when it exits, so no live credential is left on disk without a runner. */
|
|
20
|
+
export declare const removeRunnerToken: (dir: string) => Promise<void>;
|
|
21
|
+
/** The live runner's holder, or null when none holds the lock (absent, its PID gone, or its heartbeat lapsed). */
|
|
22
|
+
export declare const liveRunnerHolder: (dir: string) => Promise<LockHolder | null>;
|
|
23
|
+
/** One JSON line in `runner.log`. Diagnostics only: a failed write never stops the runner. */
|
|
24
|
+
export declare const createRunnerLog: (dir: string, clock?: () => Date) => (entry: Record<string, unknown>) => Promise<void>;
|
|
25
|
+
export type RunnerLog = ReturnType<typeof createRunnerLog>;
|
|
26
|
+
//# sourceMappingURL=workspace-runner-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-runner-store.d.ts","sourceRoot":"","sources":["../src/workspace-runner-store.ts"],"names":[],"mappings":"AAIA,OAAO,EAAyC,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAgBzF,qHAAqH;AACrH,eAAO,MAAM,qBAAqB,QAA4B,CAAC;AAQ/D,uGAAuG;AACvG,eAAO,MAAM,WAAW,GAAI,UAAU,MAAM,EAAE,QAAQ,MAAM,EAAE,WAAW,MAAM,KAAG,MAGjF,CAAC;AAEF,wFAAwF;AACxF,eAAO,MAAM,cAAc,GAAU,UAAU,MAAM,EAAE,QAAQ,MAAM,GAAG,IAAI,KAAG,OAAO,CAAC,KAAK,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAa/H,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,KAAG,MAAgC,CAAC;AAI9E,eAAO,MAAM,eAAe,GAAU,KAAK,MAAM,KAAG,OAAO,CAAC,IAAI,CAG/D,CAAC;AAEF,6GAA6G;AAC7G,eAAO,MAAM,gBAAgB,GAAU,KAAK,MAAM,EAAE,OAAO,MAAM,KAAG,OAAO,CAAC,IAAI,CAU/E,CAAC;AAEF,mEAAmE;AACnE,eAAO,MAAM,eAAe,GAAU,KAAK,MAAM,KAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAQxE,CAAC;AAEF,iFAAiF;AACjF,eAAO,MAAM,sBAAsB,GAAU,KAAK,MAAM,KAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAO/E,CAAC;AAEF,0GAA0G;AAC1G,eAAO,MAAM,iBAAiB,GAAU,KAAK,MAAM,KAAG,OAAO,CAAC,IAAI,CAEjE,CAAC;AAEF,kHAAkH;AAClH,eAAO,MAAM,gBAAgB,GAAU,KAAK,MAAM,KAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAG7E,CAAC;AAEF,8FAA8F;AAC9F,eAAO,MAAM,eAAe,GAAI,KAAK,MAAM,EAAE,QAAO,MAAM,IAAuB,MAAY,OAAO,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,OAAO,CAAC,IAAI,CAUzI,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { appendFile, chmod, mkdir, readdir, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { holderState, staleWhenHeartbeatLapsed } from './slate-lock.js';
|
|
5
|
+
/**
|
|
6
|
+
* Where the workspace runner keeps its state on this Mac: `<data root>/workspace-runner/<origin key>-<workspace>/`,
|
|
7
|
+
* a folder only this user can read (0700), holding
|
|
8
|
+
* - `lock`: the single-instance lock (alive while the runner's PID is and its heartbeat, the mtime, is fresh);
|
|
9
|
+
* - `token`: the workspace's newest upload token (0600), written by every `12ui workspace draw` for that workspace
|
|
10
|
+
* and re-read by the runner before each heartbeat, so a fresher token reaches a running runner without a restart;
|
|
11
|
+
* its mtime is when it was written, and the runner deletes it when it exits;
|
|
12
|
+
* - `codex/<pgid>.json`: each Codex image call the runner has running, by its process group, so a call a killed
|
|
13
|
+
* runner left behind can be found and stopped (workspace-runner-children.ts);
|
|
14
|
+
* - `runner.log`: what it did, JSON lines, rotated at 1 MiB (its stdio goes nowhere).
|
|
15
|
+
* The token is never put on a command line or in the environment of the detached process.
|
|
16
|
+
*/
|
|
17
|
+
const TOKEN_PATTERN = /^wp_[0-9A-Za-z]{22}$/u;
|
|
18
|
+
/** A workspace's public key as the plan names it (`wk_` and 8 hex on design.12ui.com): path-safe by construction. */
|
|
19
|
+
export const WORKSPACE_KEY_PATTERN = /^wk_[0-9A-Za-z]{1,32}$/u;
|
|
20
|
+
const LOG_LIMIT_BYTES = 1024 * 1024;
|
|
21
|
+
const sha256 = (value) => createHash('sha256').update(value).digest('hex');
|
|
22
|
+
const runnersRoot = (dataRoot) => path.join(dataRoot, 'workspace-runner');
|
|
23
|
+
const originKey = (origin) => sha256(origin).slice(0, 12);
|
|
24
|
+
/** The origin is part of the key, so a local proof server and design.12ui.com never share a runner. */
|
|
25
|
+
export const runnerDirOf = (dataRoot, origin, workspace) => {
|
|
26
|
+
if (!WORKSPACE_KEY_PATTERN.test(workspace))
|
|
27
|
+
throw new Error(`${JSON.stringify(workspace)} is not a workspace key.`);
|
|
28
|
+
return path.join(runnersRoot(dataRoot), `${originKey(origin)}-${workspace}`);
|
|
29
|
+
};
|
|
30
|
+
/** Every runner folder on this Mac, or only those of one origin: `{dir, workspace}`. */
|
|
31
|
+
export const listRunnerDirs = async (dataRoot, origin) => {
|
|
32
|
+
let names;
|
|
33
|
+
try {
|
|
34
|
+
names = await readdir(runnersRoot(dataRoot));
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (error.code === 'ENOENT')
|
|
38
|
+
return [];
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
return names.flatMap((name) => {
|
|
42
|
+
const match = /^([0-9a-f]{12})-(wk_[0-9A-Za-z]{1,32})$/u.exec(name);
|
|
43
|
+
if (!match || (origin !== null && match[1] !== originKey(origin)))
|
|
44
|
+
return [];
|
|
45
|
+
return [{ dir: path.join(runnersRoot(dataRoot), name), workspace: match[2] }];
|
|
46
|
+
}).sort((a, b) => a.dir.localeCompare(b.dir));
|
|
47
|
+
};
|
|
48
|
+
export const runnerLockFile = (dir) => path.join(dir, 'lock');
|
|
49
|
+
const tokenFile = (dir) => path.join(dir, 'token');
|
|
50
|
+
const logFile = (dir) => path.join(dir, 'runner.log');
|
|
51
|
+
export const ensureRunnerDir = async (dir) => {
|
|
52
|
+
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
53
|
+
await chmod(dir, 0o700);
|
|
54
|
+
};
|
|
55
|
+
/** Replace the token atomically (0600): a reader sees the old token or the new one, never half of either. */
|
|
56
|
+
export const writeRunnerToken = async (dir, token) => {
|
|
57
|
+
if (!TOKEN_PATTERN.test(token))
|
|
58
|
+
throw new Error('Refusing to store something that is not an upload token.');
|
|
59
|
+
const file = tokenFile(dir);
|
|
60
|
+
const temporary = `${file}.${randomUUID()}.tmp`;
|
|
61
|
+
try {
|
|
62
|
+
await writeFile(temporary, `${token}\n`, { flag: 'wx', mode: 0o600 });
|
|
63
|
+
await rename(temporary, file);
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
await rm(temporary, { force: true });
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
/** The stored token, or null when it is missing or not a token. */
|
|
70
|
+
export const readRunnerToken = async (dir) => {
|
|
71
|
+
try {
|
|
72
|
+
const value = (await readFile(tokenFile(dir), 'utf8')).trim();
|
|
73
|
+
return TOKEN_PATTERN.test(value) ? value : null;
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
if (error.code === 'ENOENT')
|
|
77
|
+
return null;
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
/** When the stored token was written (its mtime), or null when there is none. */
|
|
82
|
+
export const runnerTokenWrittenAtMs = async (dir) => {
|
|
83
|
+
try {
|
|
84
|
+
return (await stat(tokenFile(dir))).mtimeMs;
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
if (error.code === 'ENOENT')
|
|
88
|
+
return null;
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
/** The runner deletes the token when it exits, so no live credential is left on disk without a runner. */
|
|
93
|
+
export const removeRunnerToken = async (dir) => {
|
|
94
|
+
await rm(tokenFile(dir), { force: true });
|
|
95
|
+
};
|
|
96
|
+
/** The live runner's holder, or null when none holds the lock (absent, its PID gone, or its heartbeat lapsed). */
|
|
97
|
+
export const liveRunnerHolder = async (dir) => {
|
|
98
|
+
const state = await holderState(runnerLockFile(dir), staleWhenHeartbeatLapsed);
|
|
99
|
+
return state.state === 'live' ? state.holder ?? null : null;
|
|
100
|
+
};
|
|
101
|
+
/** One JSON line in `runner.log`. Diagnostics only: a failed write never stops the runner. */
|
|
102
|
+
export const createRunnerLog = (dir, clock = () => new Date()) => async (entry) => {
|
|
103
|
+
const file = logFile(dir);
|
|
104
|
+
try {
|
|
105
|
+
const size = await stat(file).then((info) => info.size, () => 0);
|
|
106
|
+
if (size > LOG_LIMIT_BYTES) {
|
|
107
|
+
await rm(`${file}.1`, { force: true });
|
|
108
|
+
await rename(file, `${file}.1`);
|
|
109
|
+
}
|
|
110
|
+
await appendFile(file, `${JSON.stringify({ at: clock().toISOString(), pid: process.pid, ...entry })}\n`, { mode: 0o600 });
|
|
111
|
+
}
|
|
112
|
+
catch { /* the log is diagnostics only */ }
|
|
113
|
+
};
|
|
114
|
+
//# sourceMappingURL=workspace-runner-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-runner-store.js","sourceRoot":"","sources":["../src/workspace-runner-store.ts"],"names":[],"mappings":"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"}
|
package/open-design.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"specVersion": "1.0.0",
|
|
4
4
|
"name": "12ui-design",
|
|
5
5
|
"title": "12UI Design",
|
|
6
|
-
"version": "0.2.
|
|
6
|
+
"version": "0.2.78",
|
|
7
7
|
"description": "Explore corpus-grounded interface directions, choose one, and convert it into production-ready responsive or native artifacts with 12UI.",
|
|
8
8
|
"author": {
|
|
9
9
|
"name": "Just Every",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"command": "npx",
|
|
50
50
|
"args": [
|
|
51
51
|
"-y",
|
|
52
|
-
"@12ui/design@0.2.
|
|
52
|
+
"@12ui/design@0.2.78",
|
|
53
53
|
"mcp"
|
|
54
54
|
]
|
|
55
55
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import sharp from 'sharp';
|
|
2
|
+
|
|
3
|
+
// How a native image that came back in another shape than its frame meets the frame without distortion (one module
|
|
4
|
+
// for the policy, CLAUDE.md item 9). The Design workspace's options use it (`aspect-preserving-lanczos3`); conversion
|
|
5
|
+
// keeps the exact stretch, whose geometry must map onto its source frame.
|
|
6
|
+
//
|
|
7
|
+
// Never stretch: the image is scaled by one factor on both axes. Cutting it to the frame's shape (cover) loses what
|
|
8
|
+
// the cut strips hold, so it is chosen only with proof that they hold nothing: every pixel of every strip within
|
|
9
|
+
// `BLANK_DELTA` of its strip's median colour on every channel. One ink pixel is no proof (CLAUDE.md item 8): a share of
|
|
10
|
+
// stray pixels let a 69 px strip holding 12 pixels of a heart button's faint outline count as blank (0.2.78 gate,
|
|
11
|
+
// 1606x979 into 1536x1024). Without that proof the whole image is kept (contain) and the frame's spare side is filled
|
|
12
|
+
// with the colour of the image's own edge there, so nothing drawn is lost.
|
|
13
|
+
|
|
14
|
+
/** A channel difference from a strip's median colour beyond which a pixel is ink. */
|
|
15
|
+
export const BLANK_DELTA = 24;
|
|
16
|
+
/** How many pixels deep an edge is sampled for the colour of the spare side. */
|
|
17
|
+
const EDGE_DEPTH = 2;
|
|
18
|
+
|
|
19
|
+
const rgbOf = async (image, region) => {
|
|
20
|
+
const {data, info} = await sharp(image).extract(region).removeAlpha().raw().toBuffer({resolveWithObject: true});
|
|
21
|
+
return {data, channels: info.channels, count: info.width * info.height};
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** The per-channel median colour of the pixels in `region`. */
|
|
25
|
+
const medianColour = ({data, channels, count}) => {
|
|
26
|
+
const colour = [];
|
|
27
|
+
for (let channel = 0; channel < 3; channel += 1) {
|
|
28
|
+
const histogram = new Uint32Array(256);
|
|
29
|
+
for (let index = 0; index < count; index += 1) histogram[data[index * channels + Math.min(channel, channels - 1)]] += 1;
|
|
30
|
+
let seen = 0, value = 0;
|
|
31
|
+
while (value < 255 && seen + histogram[value] <= count / 2) { seen += histogram[value]; value += 1; }
|
|
32
|
+
colour.push(value);
|
|
33
|
+
}
|
|
34
|
+
return colour;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** Whether a strip holds nothing: every pixel within `BLANK_DELTA` of its median colour. Any ink pixel vetoes the cut. */
|
|
38
|
+
export const stripIsBlank = async (image, region) => {
|
|
39
|
+
if (region.width <= 0 || region.height <= 0) return true;
|
|
40
|
+
const pixels = await rgbOf(image, region);
|
|
41
|
+
const [r, g, b] = medianColour(pixels);
|
|
42
|
+
for (let index = 0; index < pixels.count; index += 1) {
|
|
43
|
+
const at = index * pixels.channels;
|
|
44
|
+
const c = (offset) => pixels.data[at + Math.min(offset, pixels.channels - 1)];
|
|
45
|
+
if (Math.abs(c(0) - r) > BLANK_DELTA || Math.abs(c(1) - g) > BLANK_DELTA || Math.abs(c(2) - b) > BLANK_DELTA) return false;
|
|
46
|
+
}
|
|
47
|
+
return true;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const hex = (colour) => `#${colour.map((value) => value.toString(16).padStart(2, '0')).join('')}`;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The cover window: the largest part of the image in the frame's shape, centred, and the strips outside it (in the
|
|
54
|
+
* image's own pixels). An image already in the frame's shape to within a pixel has no strips.
|
|
55
|
+
*/
|
|
56
|
+
export const coverWindow = (actual, expected) => {
|
|
57
|
+
const {width: aw, height: ah} = actual, {width: fw, height: fh} = expected;
|
|
58
|
+
if (aw * fh > ah * fw) {
|
|
59
|
+
const kw = Math.min(aw, Math.round(ah * fw / fh)), left = Math.floor((aw - kw) / 2);
|
|
60
|
+
return {window: {left, top: 0, width: kw, height: ah}, strips: [{left: 0, top: 0, width: left, height: ah}, {left: left + kw, top: 0, width: aw - left - kw, height: ah}], axis: 'x'};
|
|
61
|
+
}
|
|
62
|
+
const kh = Math.min(ah, Math.round(aw * fh / fw)), top = Math.floor((ah - kh) / 2);
|
|
63
|
+
return {window: {left: 0, top, width: aw, height: kh}, strips: [{left: 0, top: 0, width: aw, height: top}, {left: 0, top: top + kh, width: aw, height: ah - top - kh}], axis: 'y'};
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Fit `original` (PNG bytes) to `expected` without distortion: the uniform scale when its shape is the frame's (to
|
|
68
|
+
* within a pixel), the cover crop when every cut strip is blank, else the contain fit padded with the edge colours.
|
|
69
|
+
* Returns the PNG and the record of what was done.
|
|
70
|
+
*/
|
|
71
|
+
export async function fitToFrame(original, expected) {
|
|
72
|
+
const meta = await sharp(original).metadata();
|
|
73
|
+
const actual = {width: meta.width, height: meta.height};
|
|
74
|
+
const {window, strips, axis} = coverWindow(actual, expected);
|
|
75
|
+
/** @type {import('sharp').ResizeOptions} */
|
|
76
|
+
const resize = {fit: 'fill', kernel: 'lanczos3'};
|
|
77
|
+
if (strips.every((strip) => strip.width <= 0 || strip.height <= 0)) {
|
|
78
|
+
const png = await sharp(original).resize(expected.width, expected.height, resize).png().toBuffer();
|
|
79
|
+
return {png, normalization: {name: 'uniform-scale', kernel: 'lanczos3', output: expected, scale: expected.width / actual.width}};
|
|
80
|
+
}
|
|
81
|
+
const blank = [];
|
|
82
|
+
for (const strip of strips) blank.push(await stripIsBlank(original, strip));
|
|
83
|
+
if (blank.every(Boolean)) {
|
|
84
|
+
const png = await sharp(original).extract(window).resize(expected.width, expected.height, resize).png().toBuffer();
|
|
85
|
+
return {png, normalization: {name: 'cover-crop', kernel: 'lanczos3', output: expected, scale: expected.width / window.width, crop: window}};
|
|
86
|
+
}
|
|
87
|
+
// Contain: the whole image at one scale, the spare side filled with the colour of the image's own edge beside it.
|
|
88
|
+
const scale = Math.min(expected.width / actual.width, expected.height / actual.height);
|
|
89
|
+
const width = Math.min(expected.width, Math.max(1, Math.round(actual.width * scale)));
|
|
90
|
+
const height = Math.min(expected.height, Math.max(1, Math.round(actual.height * scale)));
|
|
91
|
+
const spare = axis === 'y' ? expected.width - width : expected.height - height;
|
|
92
|
+
const before = Math.floor(spare / 2), after = spare - before;
|
|
93
|
+
const edges = axis === 'y'
|
|
94
|
+
? [{left: 0, top: 0, width: Math.min(EDGE_DEPTH, actual.width), height: actual.height}, {left: Math.max(0, actual.width - EDGE_DEPTH), top: 0, width: Math.min(EDGE_DEPTH, actual.width), height: actual.height}]
|
|
95
|
+
: [{left: 0, top: 0, width: actual.width, height: Math.min(EDGE_DEPTH, actual.height)}, {left: 0, top: Math.max(0, actual.height - EDGE_DEPTH), width: actual.width, height: Math.min(EDGE_DEPTH, actual.height)}];
|
|
96
|
+
const [first, second] = await Promise.all(edges.map(async (edge) => medianColour(await rgbOf(original, edge))));
|
|
97
|
+
const fill = (colour) => ({r: colour[0], g: colour[1], b: colour[2], alpha: 1});
|
|
98
|
+
const pad = axis === 'y' ? {left: before, right: after, top: 0, bottom: 0} : {left: 0, right: 0, top: before, bottom: after};
|
|
99
|
+
let png = await sharp(original).resize(width, height, resize).png().toBuffer();
|
|
100
|
+
if (before > 0) png = await sharp(png).extend(axis === 'y' ? {left: before, background: fill(first)} : {top: before, background: fill(first)}).png().toBuffer();
|
|
101
|
+
if (after > 0) png = await sharp(png).extend(axis === 'y' ? {right: after, background: fill(second)} : {bottom: after, background: fill(second)}).png().toBuffer();
|
|
102
|
+
return {png, normalization: {name: 'contain-pad', kernel: 'lanczos3', output: expected, scale, pad, colours: [hex(first), hex(second)]}};
|
|
103
|
+
}
|
|
@@ -8,13 +8,26 @@ import {filesUnder} from './native-files.mjs';
|
|
|
8
8
|
import {collectNative,packNativeReceipt} from './native-collect.mjs';
|
|
9
9
|
|
|
10
10
|
// Based on the successful C-N03 study invocation; separate from the no-tool planner.
|
|
11
|
+
// The call is unattended (`-a never`) and its prompt carries text from outside (a person's typed edit, a plan built
|
|
12
|
+
// from a source image), so its sandbox is read-only: the model may read the attached images and call the image tool,
|
|
13
|
+
// whose output Codex itself records in the rollout and its own home, but any command it is talked into cannot write.
|
|
14
|
+
// Proven with real Codex 0.159.3 (docs/slate-remote.md, "Image calls are read-only").
|
|
15
|
+
export const NATIVE_SANDBOX = 'read-only';
|
|
11
16
|
export function nativeArgv(spec, finalPath) {
|
|
12
17
|
return ['-a', 'never', 'exec', '--ignore-user-config', '--ignore-rules', '--strict-config', '-C', spec.workDir,
|
|
13
|
-
'--skip-git-repo-check', '-s',
|
|
18
|
+
'--skip-git-repo-check', '-s', NATIVE_SANDBOX, '-m', 'gpt-6-astra', '-c', 'model_reasoning_effort="medium"',
|
|
14
19
|
'-c', 'model_provider="openai"', '-c', 'forced_login_method="chatgpt"', '-c', 'project_doc_max_bytes=0',
|
|
15
20
|
'--enable', 'skip_host_skill_discovery', '--json', '--color', 'never', '--output-last-message', finalPath,
|
|
16
21
|
...(spec.referencePaths ?? [spec.referencePath]).flatMap(file => ['--image', file]), '-'];
|
|
17
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* One native image call: `codex exec` in its own process group, its prompt on stdin, its events and stderr kept in the
|
|
25
|
+
* call directory. Optional in `spec`:
|
|
26
|
+
* - `interruptOnSignal` (default true): a SIGINT or SIGTERM of this process stops the call. A caller that finishes its
|
|
27
|
+
* calls on a signal itself (the Design workspace runner) passes false, so the call runs to its own end or timeout.
|
|
28
|
+
* - `onSpawn({pgid, callDir})` / `onExit({pgid})`: told the child's process group (it leads its own) once it runs and
|
|
29
|
+
* once it is gone, so a caller can find a call it left behind. A failing hook never fails the call.
|
|
30
|
+
*/
|
|
18
31
|
export async function invokeNative(spec, {callDir, persistMetadata}) {
|
|
19
32
|
await fs.mkdir(spec.workDir, {recursive: true});
|
|
20
33
|
const auth = spec.auth;
|
|
@@ -27,15 +40,21 @@ export async function invokeNative(spec, {callDir, persistMetadata}) {
|
|
|
27
40
|
const prompt = spec.prompt;
|
|
28
41
|
let timedOut = false, killTimer;
|
|
29
42
|
const child = spawn(spec.binary, argv, {cwd: spec.workDir, env: subscriptionEnv(auth.home), detached: true, stdio: ['pipe', 'pipe', 'pipe']});
|
|
30
|
-
|
|
43
|
+
// ESRCH: the group is gone. EPERM: macOS answers so for a group whose members are all zombies.
|
|
44
|
+
const stop = signal => { if (child.pid) try { process.kill(-child.pid, signal); } catch (error) { if (error.code !== 'ESRCH' && error.code !== 'EPERM') throw error; } };
|
|
31
45
|
const terminate = () => { stop('SIGTERM'); killTimer = setTimeout(() => stop('SIGKILL'), 5000); };
|
|
46
|
+
const hook = async (name, value) => { try { await spec[name]?.(value); } catch { /* the caller's bookkeeping only */ } };
|
|
47
|
+
const closed = new Promise(resolve => { child.once('error', error => resolve({spawnError: String(error)})); child.once('close', (exitCode, signal) => resolve({exitCode, signal})); });
|
|
32
48
|
child.stdout.pipe(stdout); child.stderr.pipe(stderr); child.stdin.on('error', () => {}); child.stdin.end(prompt);
|
|
33
49
|
const timer = setTimeout(() => { timedOut = true; terminate(); }, spec.timeoutMs);
|
|
50
|
+
const interruptible = spec.interruptOnSignal !== false;
|
|
34
51
|
const interrupted = () => terminate();
|
|
35
|
-
process.once('SIGINT', interrupted); process.once('SIGTERM', interrupted);
|
|
36
|
-
|
|
52
|
+
if (interruptible) { process.once('SIGINT', interrupted); process.once('SIGTERM', interrupted); }
|
|
53
|
+
if (child.pid) await hook('onSpawn', {pgid: child.pid, callDir});
|
|
54
|
+
const result = await closed;
|
|
37
55
|
clearTimeout(timer); clearTimeout(killTimer); stop('SIGTERM');
|
|
38
|
-
process.removeListener('SIGINT', interrupted); process.removeListener('SIGTERM', interrupted);
|
|
56
|
+
if (interruptible) { process.removeListener('SIGINT', interrupted); process.removeListener('SIGTERM', interrupted); }
|
|
57
|
+
if (child.pid) await hook('onExit', {pgid: child.pid});
|
|
39
58
|
await Promise.all([finished(stdout), finished(stderr)]);
|
|
40
59
|
const events=(await fs.readFile(path.join(callDir,'events.jsonl'),'utf8')).split('\n').filter(Boolean).flatMap(line=>{try{return [JSON.parse(line)];}catch{return [{type:'malformed-event'}];}});
|
|
41
60
|
const diagnostics=events.filter(event=>['error','turn.failed','malformed-event'].includes(event.type));
|
|
@@ -7,18 +7,27 @@ import {invokeNative} from './native-invoke.mjs';
|
|
|
7
7
|
import {collectNative,packNativeReceipt} from './native-collect.mjs';
|
|
8
8
|
import {filesUnder,inputFileNames} from './native-files.mjs';
|
|
9
9
|
import {sha256,pngDimensions,parseNativeRecords} from './native-records.mjs';
|
|
10
|
+
import {fitToFrame} from './native-frame.mjs';
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
/** How a native image meets its frame: conversion's exact stretch (the default), or the aspect-preserving fit (native-frame.mjs). */
|
|
13
|
+
export const NATIVE_NORMALIZATIONS=Object.freeze({stretch:'exact-stretch-lanczos3',preserveAspect:'aspect-preserving-lanczos3'});
|
|
14
|
+
/** @typedef {typeof NATIVE_NORMALIZATIONS[keyof typeof NATIVE_NORMALIZATIONS]} NativeNormalization */
|
|
15
|
+
/** @param {Buffer} bytes @param {{normalization?: NativeNormalization}} [options] */
|
|
16
|
+
export async function decodeNativeResponse(bytes,{normalization=NATIVE_NORMALIZATIONS.stretch}={}) {
|
|
17
|
+
if(!Object.values(NATIVE_NORMALIZATIONS).includes(normalization)) throw Error('Unknown native normalization: '+normalization);
|
|
12
18
|
const receipt=JSON.parse(bytes.toString('utf8'));
|
|
13
19
|
if(!['exact-size-pass','size-mismatch'].includes(receipt.state)||receipt.outputs.length!==1) throw Error('Native image call requires inspection: '+receipt.state);
|
|
14
20
|
if(receipt.model!=='gpt-6-astra'||receipt.effort!=='medium')throw Error('Native coordinator model/effort does not match requested execution');
|
|
15
21
|
const original=Buffer.from(receipt.outputs[0].base64,'base64');
|
|
16
22
|
if(sha256(original)!==receipt.outputs[0].sha256) throw Error('Native original custody mismatch');
|
|
17
23
|
const actual=pngDimensions(original), expected=receipt.geometry.raw.expected;
|
|
18
|
-
const
|
|
19
|
-
|
|
24
|
+
const fitted=normalization===NATIVE_NORMALIZATIONS.preserveAspect
|
|
25
|
+
?await fitToFrame(original,expected)
|
|
26
|
+
:{png:await sharp(original).resize(expected.width,expected.height,{fit:'fill',kernel:'lanczos3'}).png().toBuffer(),normalization:{name:'exact-stretch',kernel:'lanczos3',output:expected,scaleX:expected.width/actual.width,scaleY:expected.height/actual.height}};
|
|
27
|
+
return {png:fitted.png,usage:{codexTokens:receipt.tokenUsage??[],imageUsage:null},revisedPrompt:receipt.generations[0].revisedPrompt??null,native:{originalPath:receipt.outputs[0].path,originalSha256:sha256(original),originalDimensions:actual,normalization:fitted.normalization,model:receipt.model,effort:receipt.effort,imageModel:null,invocationCount:receipt.invocationCount}};
|
|
20
28
|
}
|
|
21
|
-
|
|
29
|
+
/** `processOptions` reaches the call's `codex exec` child: `interruptOnSignal`, `onSpawn`, `onExit` (native-invoke.mjs). */
|
|
30
|
+
export async function executeNativeCall({request,input,callId,store,codexBinary='codex',codexHome,timeoutMs=1800000,processOptions={}}) {
|
|
22
31
|
let auth;
|
|
23
32
|
return store.runCall(callId,request,async({callDir,persistMetadata})=>{
|
|
24
33
|
// One reference is the conversion case; a list attaches every image in order (design workspace edits and variations).
|
|
@@ -29,7 +38,7 @@ export async function executeNativeCall({request,input,callId,store,codexBinary=
|
|
|
29
38
|
const referencePath=referencePaths[0];
|
|
30
39
|
const inspect=referencePaths.length===1?`Use tools.view_image once to inspect ${referencePath}.`:`Use tools.view_image once for each of these files, in this order: ${referencePaths.join(', ')}.`;
|
|
31
40
|
const prompt=`This is an asset-execution task. ${inspect} Then invoke tools.image_gen__imagegen exactly once with referenced_image_paths=[${referencePaths.map(file=>JSON.stringify(file)).join(', ')}] and the following prompt copied verbatim. Do not use shell, web, apps, other image providers, or image-editing code. No retries, extra calls, HTML work, or visual repair. If unavailable or unsuccessful, report failure. Emit the native image result with generatedImage and finish. Do not print base64.\n\nIMAGE PROMPT:\n${request.prompt}\n`;
|
|
32
|
-
return invokeNative({auth,binary:codexBinary,workDir:path.join(callDir,'work'),referencePath,referencePaths,prompt,timeoutMs,expected:request.expected},{callDir,persistMetadata});
|
|
41
|
+
return invokeNative({...processOptions,auth,binary:codexBinary,workDir:path.join(callDir,'work'),referencePath,referencePaths,prompt,timeoutMs,expected:request.expected},{callDir,persistMetadata});
|
|
33
42
|
},{prepare:async({callDir})=>{
|
|
34
43
|
const authHome=codexHome??process.env.CODEX_HOME??path.join(os.homedir(),'.codex');
|
|
35
44
|
auth=await prepareHome({authHome,binary:codexBinary,runDir:callDir});
|
|
@@ -3,7 +3,7 @@ import { hostname } from 'node:os';
|
|
|
3
3
|
import { join, dirname } from 'node:path';
|
|
4
4
|
import { randomUUID } from 'node:crypto';
|
|
5
5
|
import { canonical, hash, readJson, writeJson, writeBytes, syncDirectory } from './run-store-io.mjs';
|
|
6
|
-
export { hash, readJson, writeJson } from './run-store-io.mjs';
|
|
6
|
+
export { canonical, hash, readJson, writeJson } from './run-store-io.mjs';
|
|
7
7
|
/** @typedef {{bytes: Buffer, metadata?: any}} Response */
|
|
8
8
|
/** @param {string} message @param {string} code */
|
|
9
9
|
function failure(message, code) { return Object.assign(new Error(message), { code }); }
|
|
@@ -77,6 +77,26 @@ export function createStore(runDir) {
|
|
|
77
77
|
},
|
|
78
78
|
};
|
|
79
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* The response a call id already received, read back with the request it was received for, when its custody is
|
|
82
|
+
* intact (the checks `runCall` makes before it reuses one); null when the id received nothing. It only reads: a caller
|
|
83
|
+
* whose own request differs (`REQUEST_CHANGED`) decides whether that paid response serves it, and `runCall` keeps the
|
|
84
|
+
* id's identity as it is.
|
|
85
|
+
* @param {string} runDir @param {string} id
|
|
86
|
+
* @returns {Promise<{callDir: string, request: any, requestHash: string, bytes: Buffer, metadata: any} | null>}
|
|
87
|
+
*/
|
|
88
|
+
export async function readReceived(runDir, id) {
|
|
89
|
+
const callDir = join(runDir, 'calls', hash(id));
|
|
90
|
+
const state = await readJson(join(callDir, 'state.json'));
|
|
91
|
+
if (!state || state.id !== id || state.status === 'prepared') return null;
|
|
92
|
+
const [request, custody] = await Promise.all([readJson(join(callDir, 'request.json')), readJson(join(callDir, 'custody.json'))]);
|
|
93
|
+
let bytes = null;
|
|
94
|
+
try { bytes = await readFile(join(callDir, 'response.bin')); }
|
|
95
|
+
catch (error) { if (/** @type {NodeJS.ErrnoException} */ (error).code !== 'ENOENT') throw error; }
|
|
96
|
+
if (!bytes || !request || hash(canonical(request)) !== state.requestHash) return null;
|
|
97
|
+
if (custody?.requestHash !== state.requestHash || custody.length !== bytes.length || custody.responseHash !== hash(bytes)) return null;
|
|
98
|
+
return { callDir, request, requestHash: state.requestHash, bytes, metadata: custody.metadata };
|
|
99
|
+
}
|
|
80
100
|
/** @template T @param {string} runDir @param {() => Promise<T>} callback @returns {Promise<T>} */
|
|
81
101
|
export async function withRunLock(runDir, callback) {
|
|
82
102
|
await mkdir(runDir, { recursive: true });
|