@floless/app 0.20.0 → 0.21.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/dist/floless-server.cjs +61 -92
- package/dist/web/aware.js +10 -4
- package/package.json +1 -1
package/dist/floless-server.cjs
CHANGED
|
@@ -50958,6 +50958,17 @@ function appProvider(id) {
|
|
|
50958
50958
|
return "other";
|
|
50959
50959
|
}
|
|
50960
50960
|
}
|
|
50961
|
+
function appBaked(id) {
|
|
50962
|
+
try {
|
|
50963
|
+
const dir = appDir(id);
|
|
50964
|
+
const sourcePath = firstWithExt(dir, ".app") ?? firstWithExt(dir, ".flo");
|
|
50965
|
+
if (!sourcePath) return false;
|
|
50966
|
+
const src = asRecord((0, import_yaml.parse)((0, import_node_fs3.readFileSync)(sourcePath, "utf8")));
|
|
50967
|
+
return src["exposes-as-agent"] === true;
|
|
50968
|
+
} catch {
|
|
50969
|
+
return false;
|
|
50970
|
+
}
|
|
50971
|
+
}
|
|
50961
50972
|
function readApp(id) {
|
|
50962
50973
|
const dir = appDir(id);
|
|
50963
50974
|
const sourcePath = firstWithExt(dir, ".app") ?? firstWithExt(dir, ".flo");
|
|
@@ -51933,6 +51944,41 @@ var aware = {
|
|
|
51933
51944
|
if (code !== 0) throw new AwareError(`aware app uninstall failed (exit ${code})`, { stdout, stderr });
|
|
51934
51945
|
return { output: stdout.trim() };
|
|
51935
51946
|
},
|
|
51947
|
+
/**
|
|
51948
|
+
* Rename an installed app in place (`aware app rename <old> <new>`, CLI ≥ 0.68).
|
|
51949
|
+
* AWARE owns the whole move — the app dir, the source `app:` field, the
|
|
51950
|
+
* regenerated lock (so the Run gate stays green, no drift), and the synthesized
|
|
51951
|
+
* agent of a baked (`exposes-as-agent`) app. `compiled` reflects whether AWARE
|
|
51952
|
+
* refreshed the lock (false → the app needs a manual Compile). Throws
|
|
51953
|
+
* AwareUnsupportedError on a pre-0.68 CLI so app-lifecycle can say "update AWARE"
|
|
51954
|
+
* instead of treating an old CLI as a hard fault.
|
|
51955
|
+
*/
|
|
51956
|
+
async rename(oldId, newId) {
|
|
51957
|
+
const { code, stdout, stderr } = await runRaw(["app", "rename", assertId(oldId), assertId(newId)]);
|
|
51958
|
+
if (code !== 0) {
|
|
51959
|
+
if (isInvokeUnsupported({ stdout, stderr })) {
|
|
51960
|
+
throw new AwareUnsupportedError("aware CLI does not support `app rename` (needs \u2265 0.68) \u2014 update AWARE", { stdout, stderr });
|
|
51961
|
+
}
|
|
51962
|
+
throw new AwareError(`aware app rename ${oldId} \u2192 ${newId} failed (exit ${code})`, { stdout, stderr, code });
|
|
51963
|
+
}
|
|
51964
|
+
return { compiled: /lock refreshed/i.test(stdout), output: stdout.trim() };
|
|
51965
|
+
},
|
|
51966
|
+
/**
|
|
51967
|
+
* Duplicate an installed app into an independent copy (`aware app duplicate
|
|
51968
|
+
* <src> <new>`, CLI ≥ 0.68). The original is untouched; a baked copy gets its
|
|
51969
|
+
* own synthesized agent. Same `compiled` + unsupported-degradation contract as
|
|
51970
|
+
* rename.
|
|
51971
|
+
*/
|
|
51972
|
+
async duplicate(srcId, newId) {
|
|
51973
|
+
const { code, stdout, stderr } = await runRaw(["app", "duplicate", assertId(srcId), assertId(newId)]);
|
|
51974
|
+
if (code !== 0) {
|
|
51975
|
+
if (isInvokeUnsupported({ stdout, stderr })) {
|
|
51976
|
+
throw new AwareUnsupportedError("aware CLI does not support `app duplicate` (needs \u2265 0.68) \u2014 update AWARE", { stdout, stderr });
|
|
51977
|
+
}
|
|
51978
|
+
throw new AwareError(`aware app duplicate ${srcId} \u2192 ${newId} failed (exit ${code})`, { stdout, stderr, code });
|
|
51979
|
+
}
|
|
51980
|
+
return { compiled: /lock refreshed/i.test(stdout), output: stdout.trim() };
|
|
51981
|
+
},
|
|
51936
51982
|
/**
|
|
51937
51983
|
* Install a first-party agent by registry id (`aware agent install <id>`). The
|
|
51938
51984
|
* registry install pulls a large tarball (~200s observed) — far over runRaw's 60s
|
|
@@ -52683,7 +52729,7 @@ function appVersion() {
|
|
|
52683
52729
|
return resolveVersion({
|
|
52684
52730
|
isSea: isSea2(),
|
|
52685
52731
|
sqVersionXml: readSqVersionXml(),
|
|
52686
|
-
define: true ? "0.
|
|
52732
|
+
define: true ? "0.21.0" : void 0,
|
|
52687
52733
|
pkgVersion: readPkgVersion()
|
|
52688
52734
|
});
|
|
52689
52735
|
}
|
|
@@ -52693,7 +52739,7 @@ function resolveChannel(s) {
|
|
|
52693
52739
|
return "dev";
|
|
52694
52740
|
}
|
|
52695
52741
|
function appChannel() {
|
|
52696
|
-
return resolveChannel({ isSea: isSea2(), define: true ? "0.
|
|
52742
|
+
return resolveChannel({ isSea: isSea2(), define: true ? "0.21.0" : void 0 });
|
|
52697
52743
|
}
|
|
52698
52744
|
|
|
52699
52745
|
// oauth-presets.ts
|
|
@@ -54104,49 +54150,15 @@ function appDirPath(id) {
|
|
|
54104
54150
|
function appInstalled(id) {
|
|
54105
54151
|
return APP_ID3.test(id) && (0, import_node_fs16.existsSync)(appDirPath(id));
|
|
54106
54152
|
}
|
|
54107
|
-
function findSource(dir) {
|
|
54108
|
-
const files = (0, import_node_fs16.readdirSync)(dir);
|
|
54109
|
-
const hit = files.find((f) => f.toLowerCase().endsWith(".app")) ?? files.find((f) => f.toLowerCase().endsWith(".flo"));
|
|
54110
|
-
return hit ? (0, import_node_path14.join)(dir, hit) : null;
|
|
54111
|
-
}
|
|
54112
|
-
function rewriteAppId(text, newId) {
|
|
54113
|
-
const re = /^(app:[ \t]*)(["']?)([^"'#\r\n]+?)\2([ \t]*(?:#[^\r\n]*)?)$/m;
|
|
54114
|
-
if (!re.test(text)) throw new AppLifecycleError("source has no top-level app: field", 422);
|
|
54115
|
-
return text.replace(re, (_m, pre, quote, _id, trail) => `${pre}${quote}${newId}${quote}${trail}`);
|
|
54116
|
-
}
|
|
54117
|
-
function isExposedAsAgent(dir) {
|
|
54118
|
-
const src = findSource(dir);
|
|
54119
|
-
return src ? /^exposes-as-agent:[ \t]*true\b/m.test((0, import_node_fs16.readFileSync)(src, "utf8")) : false;
|
|
54120
|
-
}
|
|
54121
54153
|
function logCascade(what, e) {
|
|
54122
54154
|
console.error(`[app-lifecycle] cascade step failed (${what}):`, e instanceof Error ? e.message : e);
|
|
54123
54155
|
}
|
|
54124
|
-
function
|
|
54125
|
-
|
|
54126
|
-
|
|
54127
|
-
|
|
54128
|
-
|
|
54129
|
-
|
|
54130
|
-
} catch {
|
|
54131
|
-
}
|
|
54132
|
-
}
|
|
54133
|
-
}
|
|
54134
|
-
}
|
|
54135
|
-
function restampSource(dir, newId) {
|
|
54136
|
-
const src = findSource(dir);
|
|
54137
|
-
if (!src) throw new AppLifecycleError("app has no source file", 422);
|
|
54138
|
-
const text = rewriteAppId((0, import_node_fs16.readFileSync)(src, "utf8"), newId);
|
|
54139
|
-
const ext = src.toLowerCase().endsWith(".flo") ? ".flo" : ".app";
|
|
54140
|
-
const newSrc = (0, import_node_path14.join)(dir, `${newId}${ext}`);
|
|
54141
|
-
(0, import_node_fs16.writeFileSync)(newSrc, text);
|
|
54142
|
-
if (src !== newSrc) {
|
|
54143
|
-
try {
|
|
54144
|
-
(0, import_node_fs16.unlinkSync)(src);
|
|
54145
|
-
} catch {
|
|
54146
|
-
}
|
|
54147
|
-
}
|
|
54148
|
-
removeLocks(dir);
|
|
54149
|
-
return newSrc;
|
|
54156
|
+
function mapAwareError(e) {
|
|
54157
|
+
const code = e instanceof AwareError ? e.detail?.code : void 0;
|
|
54158
|
+
if (code === 8) throw new AppLifecycleError("a workflow with that name already exists", 409);
|
|
54159
|
+
if (code === 7) throw new AppLifecycleError("workflow not found", 404);
|
|
54160
|
+
if (code === 3) throw new AppLifecycleError(e instanceof Error ? e.message : "invalid name", 400);
|
|
54161
|
+
throw e;
|
|
54150
54162
|
}
|
|
54151
54163
|
function assertNewId(newId) {
|
|
54152
54164
|
if (!newId || !APP_ID3.test(newId) || !safeSegment(newId)) {
|
|
@@ -54154,39 +54166,16 @@ function assertNewId(newId) {
|
|
|
54154
54166
|
}
|
|
54155
54167
|
}
|
|
54156
54168
|
var defaultDeps2 = {
|
|
54157
|
-
|
|
54169
|
+
rename: (oldId, newId) => aware.rename(oldId, newId),
|
|
54170
|
+
duplicate: (srcId, newId) => aware.duplicate(srcId, newId),
|
|
54158
54171
|
uninstall: (id) => aware.uninstall(id)
|
|
54159
54172
|
};
|
|
54160
|
-
async function tryCompile(deps, sourcePath) {
|
|
54161
|
-
try {
|
|
54162
|
-
await deps.compile(sourcePath);
|
|
54163
|
-
return true;
|
|
54164
|
-
} catch {
|
|
54165
|
-
return false;
|
|
54166
|
-
}
|
|
54167
|
-
}
|
|
54168
54173
|
async function renameApp(oldId, newId, deps = defaultDeps2) {
|
|
54169
54174
|
if (!appInstalled(oldId)) throw new AppLifecycleError(`workflow not found: ${oldId}`, 404);
|
|
54170
54175
|
assertNewId(newId);
|
|
54171
54176
|
if (newId === oldId) throw new AppLifecycleError("that is already its name", 400);
|
|
54172
54177
|
if (appInstalled(newId)) throw new AppLifecycleError(`a workflow named "${newId}" already exists`, 409);
|
|
54173
|
-
|
|
54174
|
-
throw new AppLifecycleError("this workflow is baked into an agent \u2014 rename isn\u2019t supported yet (the agent wouldn\u2019t follow). Un-bake it first, or rename it from the terminal.", 409);
|
|
54175
|
-
}
|
|
54176
|
-
const oldDir = appDirPath(oldId);
|
|
54177
|
-
const newDir = appDirPath(newId);
|
|
54178
|
-
(0, import_node_fs16.renameSync)(oldDir, newDir);
|
|
54179
|
-
let newSrc;
|
|
54180
|
-
try {
|
|
54181
|
-
newSrc = restampSource(newDir, newId);
|
|
54182
|
-
} catch (e) {
|
|
54183
|
-
try {
|
|
54184
|
-
(0, import_node_fs16.renameSync)(newDir, oldDir);
|
|
54185
|
-
} catch {
|
|
54186
|
-
}
|
|
54187
|
-
throw e;
|
|
54188
|
-
}
|
|
54189
|
-
const compiled = await tryCompile(deps, newSrc);
|
|
54178
|
+
const { compiled } = await deps.rename(oldId, newId).catch(mapAwareError);
|
|
54190
54179
|
try {
|
|
54191
54180
|
renameVisualInputs(oldId, newId);
|
|
54192
54181
|
} catch (e) {
|
|
@@ -54203,27 +54192,7 @@ async function duplicateApp(srcId, newId, deps = defaultDeps2) {
|
|
|
54203
54192
|
if (!appInstalled(srcId)) throw new AppLifecycleError(`workflow not found: ${srcId}`, 404);
|
|
54204
54193
|
assertNewId(newId);
|
|
54205
54194
|
if (appInstalled(newId)) throw new AppLifecycleError(`a workflow named "${newId}" already exists`, 409);
|
|
54206
|
-
|
|
54207
|
-
throw new AppLifecycleError("this workflow is baked into an agent \u2014 duplicate isn\u2019t supported yet (the copy\u2019s agent wouldn\u2019t be installed). Un-bake it first, or copy it from the terminal.", 409);
|
|
54208
|
-
}
|
|
54209
|
-
const newDir = appDirPath(newId);
|
|
54210
|
-
try {
|
|
54211
|
-
(0, import_node_fs16.mkdirSync)(newDir);
|
|
54212
|
-
} catch {
|
|
54213
|
-
throw new AppLifecycleError(`a workflow named "${newId}" already exists`, 409);
|
|
54214
|
-
}
|
|
54215
|
-
let newSrc;
|
|
54216
|
-
try {
|
|
54217
|
-
(0, import_node_fs16.cpSync)(appDirPath(srcId), newDir, { recursive: true });
|
|
54218
|
-
newSrc = restampSource(newDir, newId);
|
|
54219
|
-
} catch (e) {
|
|
54220
|
-
try {
|
|
54221
|
-
(0, import_node_fs16.rmSync)(newDir, { recursive: true, force: true });
|
|
54222
|
-
} catch {
|
|
54223
|
-
}
|
|
54224
|
-
throw e;
|
|
54225
|
-
}
|
|
54226
|
-
const compiled = await tryCompile(deps, newSrc);
|
|
54195
|
+
const { compiled } = await deps.duplicate(srcId, newId).catch(mapAwareError);
|
|
54227
54196
|
try {
|
|
54228
54197
|
copyVisualInputs(srcId, newId);
|
|
54229
54198
|
} catch (e) {
|
|
@@ -54233,7 +54202,7 @@ async function duplicateApp(srcId, newId, deps = defaultDeps2) {
|
|
|
54233
54202
|
}
|
|
54234
54203
|
async function deleteApp(id, deps = defaultDeps2) {
|
|
54235
54204
|
if (!appInstalled(id)) throw new AppLifecycleError(`workflow not found: ${id}`, 404);
|
|
54236
|
-
await deps.uninstall(id);
|
|
54205
|
+
await deps.uninstall(id).catch(mapAwareError);
|
|
54237
54206
|
try {
|
|
54238
54207
|
deleteVisualInputs(id);
|
|
54239
54208
|
} catch (e) {
|
|
@@ -57273,7 +57242,7 @@ async function startServer() {
|
|
|
57273
57242
|
}
|
|
57274
57243
|
});
|
|
57275
57244
|
await app.register(import_static.default, { root: WEB_ROOT, prefix: "/" });
|
|
57276
|
-
const MIN_AWARE = "0.
|
|
57245
|
+
const MIN_AWARE = "0.68.0";
|
|
57277
57246
|
const isWin3 = process.platform === "win32";
|
|
57278
57247
|
const has = (cmd) => {
|
|
57279
57248
|
try {
|
|
@@ -57499,7 +57468,7 @@ async function startServer() {
|
|
|
57499
57468
|
onSeatLost((reason) => broadcast({ type: "seat-taken", reason }));
|
|
57500
57469
|
app.get("/api/apps", async () => {
|
|
57501
57470
|
const apps = await aware.list();
|
|
57502
|
-
return { ok: true, apps: apps.map((a) => ({ ...a, provider: appProvider(a.id) })) };
|
|
57471
|
+
return { ok: true, apps: apps.map((a) => ({ ...a, provider: appProvider(a.id), baked: appBaked(a.id) })) };
|
|
57503
57472
|
});
|
|
57504
57473
|
app.get("/api/agents", async () => {
|
|
57505
57474
|
const agents = await aware.agentList();
|
package/dist/web/aware.js
CHANGED
|
@@ -583,7 +583,7 @@
|
|
|
583
583
|
// <button>s without nesting interactive elements (#85). Selection stays driven by
|
|
584
584
|
// the delegated $wfList click + comboMove highlight (aria-activedescendant).
|
|
585
585
|
const aid = escapeAttr(a.id);
|
|
586
|
-
html += `<div class="wf-option" role="option" id="wf-opt-${aid}" data-id="${aid}" data-search="${escapeAttr((a.id + ' ' + p).toLowerCase())}" tabindex="-1" aria-selected="false">`
|
|
586
|
+
html += `<div class="wf-option" role="option" id="wf-opt-${aid}" data-id="${aid}" data-baked="${a.baked ? '1' : ''}" data-search="${escapeAttr((a.id + ' ' + p).toLowerCase())}" tabindex="-1" aria-selected="false">`
|
|
587
587
|
+ `<span class="wf-option-name">${escapeHtml(a.id)}</span><span class="wf-option-meta">${meta}</span>`
|
|
588
588
|
+ `<span class="wf-option-actions">`
|
|
589
589
|
+ `<button type="button" class="wf-act act-edit" data-wf-act="rename" data-id="${aid}" tabindex="-1" data-tip="Rename" aria-label="Rename ${aid}">✎</button>`
|
|
@@ -709,7 +709,7 @@
|
|
|
709
709
|
const act = e.target.closest('[data-wf-act]');
|
|
710
710
|
if (act) {
|
|
711
711
|
const id = act.dataset.id;
|
|
712
|
-
if (act.dataset.wfAct === 'rename') openRenameWorkflow(id);
|
|
712
|
+
if (act.dataset.wfAct === 'rename') openRenameWorkflow(id, act.closest('.wf-option')?.dataset.baked === '1');
|
|
713
713
|
else if (act.dataset.wfAct === 'duplicate') openDuplicateWorkflow(id);
|
|
714
714
|
else if (act.dataset.wfAct === 'delete') confirmDeleteWorkflow(id);
|
|
715
715
|
return;
|
|
@@ -738,11 +738,17 @@
|
|
|
738
738
|
appInputValues.delete(id); dirtyBaseline.delete(id); apps.delete(id);
|
|
739
739
|
}
|
|
740
740
|
|
|
741
|
-
async function openRenameWorkflow(oldId) {
|
|
741
|
+
async function openRenameWorkflow(oldId, baked = false) {
|
|
742
742
|
closeCombo(false);
|
|
743
|
+
// A baked workflow is a reusable agent; renaming it also renames that agent, so any
|
|
744
|
+
// OTHER workflow that uses it as a node will reference the old name and need updating.
|
|
745
|
+
// Warn (informed consent) — not a blocker; the user may be renaming an unreferenced
|
|
746
|
+
// one (#226 UX review).
|
|
747
|
+
const sub = 'This is the name you pick here, and what .flo files and your terminal AI use to refer to it.'
|
|
748
|
+
+ (baked ? ' Renaming a baked workflow also renames its synthesized agent — other workflows that use it as a node will need updating.' : '');
|
|
743
749
|
const res = await formModal({
|
|
744
750
|
title: 'Rename workflow',
|
|
745
|
-
sub
|
|
751
|
+
sub,
|
|
746
752
|
fields: [{ name: 'name', label: 'New name', type: 'text', value: oldId, placeholder: 'e.g. tekla-bom-by-phase' }],
|
|
747
753
|
okLabel: 'Rename',
|
|
748
754
|
});
|