@owlmeans/viable-sdk 0.1.18-rc.8 → 0.1.18
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 +29 -11
- package/agent-meta/manifest.json +16 -0
- package/agent-meta/skills/viable-sdk/SKILL.md +1033 -0
- package/build/api/collect/types.d.ts +30 -0
- package/build/api/collect/types.d.ts.map +1 -0
- package/build/api/collect/types.js +2 -0
- package/build/api/collect/types.js.map +1 -0
- package/build/api/collect.d.ts +14 -0
- package/build/api/collect.d.ts.map +1 -0
- package/build/api/collect.js +132 -0
- package/build/api/collect.js.map +1 -0
- package/build/api/consts.local.d.ts +21 -0
- package/build/api/consts.local.d.ts.map +1 -0
- package/build/api/consts.local.js +25 -0
- package/build/api/consts.local.js.map +1 -0
- package/build/api/index.d.ts +5 -0
- package/build/api/index.d.ts.map +1 -1
- package/build/api/index.js +2 -0
- package/build/api/index.js.map +1 -1
- package/build/api/remote.d.ts +13 -6
- package/build/api/remote.d.ts.map +1 -1
- package/build/api/remote.js +302 -35
- package/build/api/remote.js.map +1 -1
- package/build/api/transport/types.d.ts +14 -0
- package/build/api/transport/types.d.ts.map +1 -0
- package/build/api/transport/types.js +2 -0
- package/build/api/transport/types.js.map +1 -0
- package/build/api/transport.d.ts +4 -0
- package/build/api/transport.d.ts.map +1 -0
- package/build/api/transport.js +32 -0
- package/build/api/transport.js.map +1 -0
- package/build/api/types.d.ts +9 -0
- package/build/api/types.d.ts.map +1 -0
- package/build/api/types.js +2 -0
- package/build/api/types.js.map +1 -0
- package/build/api/types.local.d.ts +3 -0
- package/build/api/types.local.d.ts.map +1 -0
- package/build/api/types.local.js +2 -0
- package/build/api/types.local.js.map +1 -0
- package/build/consts.d.ts +29 -14
- package/build/consts.d.ts.map +1 -1
- package/build/consts.js +30 -14
- package/build/consts.js.map +1 -1
- package/build/context/consts.local.d.ts +18 -0
- package/build/context/consts.local.d.ts.map +1 -0
- package/build/context/consts.local.js +18 -0
- package/build/context/consts.local.js.map +1 -0
- package/build/context/index.d.ts +3 -7
- package/build/context/index.d.ts.map +1 -1
- package/build/context/index.js +58 -25
- package/build/context/index.js.map +1 -1
- package/build/context/types.d.ts +26 -0
- package/build/context/types.d.ts.map +1 -0
- package/build/context/types.js +2 -0
- package/build/context/types.js.map +1 -0
- package/build/executor/boot-check.d.ts +1 -40
- package/build/executor/boot-check.d.ts.map +1 -1
- package/build/executor/boot-check.js +22 -19
- package/build/executor/boot-check.js.map +1 -1
- package/build/executor/consts.d.ts +55 -0
- package/build/executor/consts.d.ts.map +1 -0
- package/build/executor/consts.js +66 -0
- package/build/executor/consts.js.map +1 -0
- package/build/executor/consts.local.d.ts +63 -0
- package/build/executor/consts.local.d.ts.map +1 -0
- package/build/executor/consts.local.js +69 -0
- package/build/executor/consts.local.js.map +1 -0
- package/build/executor/env/types.d.ts +40 -0
- package/build/executor/env/types.d.ts.map +1 -0
- package/build/executor/env/types.js +2 -0
- package/build/executor/env/types.js.map +1 -0
- package/build/executor/env.d.ts +11 -20
- package/build/executor/env.d.ts.map +1 -1
- package/build/executor/env.js +39 -59
- package/build/executor/env.js.map +1 -1
- package/build/executor/errors.d.ts +15 -0
- package/build/executor/errors.d.ts.map +1 -1
- package/build/executor/errors.js +19 -2
- package/build/executor/errors.js.map +1 -1
- package/build/executor/files/types.d.ts +106 -0
- package/build/executor/files/types.d.ts.map +1 -0
- package/build/executor/files/types.js +2 -0
- package/build/executor/files/types.js.map +1 -0
- package/build/executor/files.d.ts +2 -50
- package/build/executor/files.d.ts.map +1 -1
- package/build/executor/files.js +225 -68
- package/build/executor/files.js.map +1 -1
- package/build/executor/git/types.d.ts +19 -0
- package/build/executor/git/types.d.ts.map +1 -0
- package/build/executor/git/types.js +2 -0
- package/build/executor/git/types.js.map +1 -0
- package/build/executor/git.d.ts +4 -36
- package/build/executor/git.d.ts.map +1 -1
- package/build/executor/git.js +206 -241
- package/build/executor/git.js.map +1 -1
- package/build/executor/glob/types.d.ts +12 -0
- package/build/executor/glob/types.d.ts.map +1 -0
- package/build/executor/glob/types.js +2 -0
- package/build/executor/glob/types.js.map +1 -0
- package/build/executor/glob.d.ts +4 -0
- package/build/executor/glob.d.ts.map +1 -0
- package/build/executor/glob.js +11 -0
- package/build/executor/glob.js.map +1 -0
- package/build/executor/health/types.d.ts +39 -0
- package/build/executor/health/types.d.ts.map +1 -0
- package/build/executor/health/types.js +2 -0
- package/build/executor/health/types.js.map +1 -0
- package/build/executor/health.d.ts +3 -83
- package/build/executor/health.d.ts.map +1 -1
- package/build/executor/health.js +76 -91
- package/build/executor/health.js.map +1 -1
- package/build/executor/index.d.ts +14 -15
- package/build/executor/index.d.ts.map +1 -1
- package/build/executor/index.js +36 -18
- package/build/executor/index.js.map +1 -1
- package/build/executor/integrity/types.d.ts +25 -0
- package/build/executor/integrity/types.d.ts.map +1 -0
- package/build/executor/integrity/types.js +2 -0
- package/build/executor/integrity/types.js.map +1 -0
- package/build/executor/integrity.d.ts +5 -15
- package/build/executor/integrity.d.ts.map +1 -1
- package/build/executor/integrity.js +28 -39
- package/build/executor/integrity.js.map +1 -1
- package/build/executor/layout/types.d.ts +69 -0
- package/build/executor/layout/types.d.ts.map +1 -0
- package/build/executor/layout/types.js +2 -0
- package/build/executor/layout/types.js.map +1 -0
- package/build/executor/layout.d.ts +9 -56
- package/build/executor/layout.d.ts.map +1 -1
- package/build/executor/layout.js +35 -82
- package/build/executor/layout.js.map +1 -1
- package/build/executor/shell/types.d.ts +76 -0
- package/build/executor/shell/types.d.ts.map +1 -0
- package/build/executor/shell/types.js +2 -0
- package/build/executor/shell/types.js.map +1 -0
- package/build/executor/shell.d.ts +3 -78
- package/build/executor/shell.d.ts.map +1 -1
- package/build/executor/shell.js +61 -91
- package/build/executor/shell.js.map +1 -1
- package/build/executor/spawn/types.d.ts +98 -0
- package/build/executor/spawn/types.d.ts.map +1 -0
- package/build/executor/spawn/types.js +2 -0
- package/build/executor/spawn/types.js.map +1 -0
- package/build/executor/spawn.d.ts +3 -103
- package/build/executor/spawn.d.ts.map +1 -1
- package/build/executor/spawn.js +192 -274
- package/build/executor/spawn.js.map +1 -1
- package/build/executor/types.d.ts +123 -0
- package/build/executor/types.d.ts.map +1 -0
- package/build/executor/types.js +2 -0
- package/build/executor/types.js.map +1 -0
- package/build/executor/types.local.d.ts +6 -0
- package/build/executor/types.local.d.ts.map +1 -0
- package/build/executor/types.local.js +2 -0
- package/build/executor/types.local.js.map +1 -0
- package/build/index.d.ts +14 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +5 -1
- package/build/index.js.map +1 -1
- package/build/project/consts.d.ts +17 -0
- package/build/project/consts.d.ts.map +1 -0
- package/build/project/consts.js +17 -0
- package/build/project/consts.js.map +1 -0
- package/build/project/consts.local.d.ts +4 -0
- package/build/project/consts.local.d.ts.map +1 -0
- package/build/project/consts.local.js +4 -0
- package/build/project/consts.local.js.map +1 -0
- package/build/project/dotenv/types.d.ts +39 -0
- package/build/project/dotenv/types.d.ts.map +1 -0
- package/build/project/dotenv/types.js +2 -0
- package/build/project/dotenv/types.js.map +1 -0
- package/build/project/dotenv.d.ts +4 -0
- package/build/project/dotenv.d.ts.map +1 -0
- package/build/project/dotenv.js +56 -0
- package/build/project/dotenv.js.map +1 -0
- package/build/project/env/types.d.ts +40 -0
- package/build/project/env/types.d.ts.map +1 -0
- package/build/project/env/types.js +2 -0
- package/build/project/env/types.js.map +1 -0
- package/build/project/env.d.ts +2 -98
- package/build/project/env.d.ts.map +1 -1
- package/build/project/env.js +63 -215
- package/build/project/env.js.map +1 -1
- package/build/project/marker/types.d.ts +33 -0
- package/build/project/marker/types.d.ts.map +1 -0
- package/build/project/marker/types.js +2 -0
- package/build/project/marker/types.js.map +1 -0
- package/build/project/marker.d.ts +4 -25
- package/build/project/marker.d.ts.map +1 -1
- package/build/project/marker.js +40 -43
- package/build/project/marker.js.map +1 -1
- package/build/project/probe/types.d.ts +22 -0
- package/build/project/probe/types.d.ts.map +1 -0
- package/build/project/probe/types.js +2 -0
- package/build/project/probe/types.js.map +1 -0
- package/build/project/probe.d.ts +4 -0
- package/build/project/probe.d.ts.map +1 -0
- package/build/project/probe.js +39 -0
- package/build/project/probe.js.map +1 -0
- package/build/project/report/types.d.ts +18 -0
- package/build/project/report/types.d.ts.map +1 -0
- package/build/project/report/types.js +2 -0
- package/build/project/report/types.js.map +1 -0
- package/build/project/report.d.ts +7 -0
- package/build/project/report.d.ts.map +1 -0
- package/build/project/report.js +68 -0
- package/build/project/report.js.map +1 -0
- package/build/project/setup/types.d.ts +32 -0
- package/build/project/setup/types.d.ts.map +1 -0
- package/build/project/setup/types.js +2 -0
- package/build/project/setup/types.js.map +1 -0
- package/build/project/setup.d.ts +4 -53
- package/build/project/setup.d.ts.map +1 -1
- package/build/project/setup.js +112 -175
- package/build/project/setup.js.map +1 -1
- package/build/project/types.d.ts +37 -0
- package/build/project/types.d.ts.map +1 -0
- package/build/project/types.js +2 -0
- package/build/project/types.js.map +1 -0
- package/build/run/consts.local.d.ts +3 -0
- package/build/run/consts.local.d.ts.map +1 -0
- package/build/run/consts.local.js +26 -0
- package/build/run/consts.local.js.map +1 -0
- package/build/run/index.d.ts +11 -69
- package/build/run/index.d.ts.map +1 -1
- package/build/run/index.js +9 -147
- package/build/run/index.js.map +1 -1
- package/build/run/local/types.d.ts +39 -0
- package/build/run/local/types.d.ts.map +1 -0
- package/build/run/local/types.js +2 -0
- package/build/run/local/types.js.map +1 -0
- package/build/run/local.d.ts +3 -0
- package/build/run/local.d.ts.map +1 -0
- package/build/run/local.js +130 -0
- package/build/run/local.js.map +1 -0
- package/build/run/process/types.d.ts +15 -0
- package/build/run/process/types.d.ts.map +1 -0
- package/build/run/process/types.js +2 -0
- package/build/run/process/types.js.map +1 -0
- package/build/run/process.d.ts +4 -0
- package/build/run/process.d.ts.map +1 -0
- package/build/run/process.js +20 -0
- package/build/run/process.js.map +1 -0
- package/build/run/serve.d.ts +14 -9
- package/build/run/serve.d.ts.map +1 -1
- package/build/run/serve.js +1 -25
- package/build/run/serve.js.map +1 -1
- package/build/run/state/types.d.ts +35 -0
- package/build/run/state/types.d.ts.map +1 -0
- package/build/run/state/types.js +2 -0
- package/build/run/state/types.js.map +1 -0
- package/build/run/state.d.ts +3 -62
- package/build/run/state.d.ts.map +1 -1
- package/build/run/state.js +96 -123
- package/build/run/state.js.map +1 -1
- package/build/run/types.d.ts +78 -0
- package/build/run/types.d.ts.map +1 -0
- package/build/run/types.js +2 -0
- package/build/run/types.js.map +1 -0
- package/build/session/consts.local.d.ts +5 -0
- package/build/session/consts.local.d.ts.map +1 -0
- package/build/session/consts.local.js +5 -0
- package/build/session/consts.local.js.map +1 -0
- package/build/session/index.d.ts +5 -10
- package/build/session/index.d.ts.map +1 -1
- package/build/session/index.js +84 -15
- package/build/session/index.js.map +1 -1
- package/build/session/questions.d.ts +16 -0
- package/build/session/questions.d.ts.map +1 -0
- package/build/session/questions.js +17 -0
- package/build/session/questions.js.map +1 -0
- package/build/session/queue.d.ts +61 -0
- package/build/session/queue.d.ts.map +1 -0
- package/build/session/queue.js +156 -0
- package/build/session/queue.js.map +1 -0
- package/build/session/tasks.d.ts +11 -30
- package/build/session/tasks.d.ts.map +1 -1
- package/build/session/tasks.js +18 -78
- package/build/session/tasks.js.map +1 -1
- package/build/session/types.d.ts +20 -0
- package/build/session/types.d.ts.map +1 -0
- package/build/session/types.js +2 -0
- package/build/session/types.js.map +1 -0
- package/build/task/consts.d.ts +12 -0
- package/build/task/consts.d.ts.map +1 -0
- package/build/task/consts.js +12 -0
- package/build/task/consts.js.map +1 -0
- package/build/task/consts.local.d.ts +19 -0
- package/build/task/consts.local.d.ts.map +1 -0
- package/build/task/consts.local.js +72 -0
- package/build/task/consts.local.js.map +1 -0
- package/build/task/driver.d.ts +1 -28
- package/build/task/driver.d.ts.map +1 -1
- package/build/task/driver.js +2 -9
- package/build/task/driver.js.map +1 -1
- package/build/task/envelope/types.d.ts +29 -0
- package/build/task/envelope/types.d.ts.map +1 -0
- package/build/task/envelope/types.js +2 -0
- package/build/task/envelope/types.js.map +1 -0
- package/build/task/envelope.d.ts +4 -34
- package/build/task/envelope.d.ts.map +1 -1
- package/build/task/envelope.js +103 -165
- package/build/task/envelope.js.map +1 -1
- package/build/task/index.d.ts +5 -0
- package/build/task/index.d.ts.map +1 -1
- package/build/task/index.js +2 -0
- package/build/task/index.js.map +1 -1
- package/build/task/inquiry/types.d.ts +38 -0
- package/build/task/inquiry/types.d.ts.map +1 -0
- package/build/task/inquiry/types.js +2 -0
- package/build/task/inquiry/types.js.map +1 -0
- package/build/task/inquiry.d.ts +5 -0
- package/build/task/inquiry.d.ts.map +1 -0
- package/build/task/inquiry.js +188 -0
- package/build/task/inquiry.js.map +1 -0
- package/build/task/types.d.ts +48 -0
- package/build/task/types.d.ts.map +1 -0
- package/build/task/types.js +2 -0
- package/build/task/types.js.map +1 -0
- package/build/tools/account/types.d.ts +24 -0
- package/build/tools/account/types.d.ts.map +1 -0
- package/build/tools/account/types.js +2 -0
- package/build/tools/account/types.js.map +1 -0
- package/build/tools/account.d.ts +4 -0
- package/build/tools/account.d.ts.map +1 -0
- package/build/tools/account.js +72 -0
- package/build/tools/account.js.map +1 -0
- package/build/tools/blueprints/types.d.ts +5 -0
- package/build/tools/blueprints/types.d.ts.map +1 -0
- package/build/tools/blueprints/types.js +2 -0
- package/build/tools/blueprints/types.js.map +1 -0
- package/build/tools/blueprints.d.ts +4 -0
- package/build/tools/blueprints.d.ts.map +1 -0
- package/build/tools/blueprints.js +23 -0
- package/build/tools/blueprints.js.map +1 -0
- package/build/tools/catalogue/types.d.ts +8 -0
- package/build/tools/catalogue/types.d.ts.map +1 -0
- package/build/tools/catalogue/types.js +2 -0
- package/build/tools/catalogue/types.js.map +1 -0
- package/build/tools/catalogue.d.ts +8 -6
- package/build/tools/catalogue.d.ts.map +1 -1
- package/build/tools/catalogue.js +2565 -213
- package/build/tools/catalogue.js.map +1 -1
- package/build/tools/config/types.d.ts +19 -0
- package/build/tools/config/types.d.ts.map +1 -0
- package/build/tools/config/types.js +2 -0
- package/build/tools/config/types.js.map +1 -0
- package/build/tools/config.d.ts +4 -0
- package/build/tools/config.d.ts.map +1 -0
- package/build/tools/config.js +42 -0
- package/build/tools/config.js.map +1 -0
- package/build/tools/consts.d.ts +61 -0
- package/build/tools/consts.d.ts.map +1 -0
- package/build/tools/consts.js +515 -0
- package/build/tools/consts.js.map +1 -0
- package/build/tools/consts.local.d.ts +131 -0
- package/build/tools/consts.local.d.ts.map +1 -0
- package/build/tools/consts.local.js +236 -0
- package/build/tools/consts.local.js.map +1 -0
- package/build/tools/control/types.d.ts +17 -0
- package/build/tools/control/types.d.ts.map +1 -0
- package/build/tools/control/types.js +2 -0
- package/build/tools/control/types.js.map +1 -0
- package/build/tools/control.d.ts +4 -0
- package/build/tools/control.d.ts.map +1 -0
- package/build/tools/control.js +44 -0
- package/build/tools/control.js.map +1 -0
- package/build/tools/feed/types.d.ts +21 -0
- package/build/tools/feed/types.d.ts.map +1 -0
- package/build/tools/feed/types.js +2 -0
- package/build/tools/feed/types.js.map +1 -0
- package/build/tools/feed.d.ts +4 -0
- package/build/tools/feed.d.ts.map +1 -0
- package/build/tools/feed.js +75 -0
- package/build/tools/feed.js.map +1 -0
- package/build/tools/git/types.d.ts +25 -0
- package/build/tools/git/types.d.ts.map +1 -0
- package/build/tools/git/types.js +2 -0
- package/build/tools/git/types.js.map +1 -0
- package/build/tools/git.d.ts +4 -0
- package/build/tools/git.d.ts.map +1 -0
- package/build/tools/git.js +83 -0
- package/build/tools/git.js.map +1 -0
- package/build/tools/handover/types.d.ts +49 -0
- package/build/tools/handover/types.d.ts.map +1 -0
- package/build/tools/handover/types.js +2 -0
- package/build/tools/handover/types.js.map +1 -0
- package/build/tools/handover.d.ts +8 -0
- package/build/tools/handover.d.ts.map +1 -0
- package/build/tools/handover.js +138 -0
- package/build/tools/handover.js.map +1 -0
- package/build/tools/host/types.d.ts +34 -0
- package/build/tools/host/types.d.ts.map +1 -0
- package/build/tools/host/types.js +2 -0
- package/build/tools/host/types.js.map +1 -0
- package/build/tools/host.d.ts +4 -0
- package/build/tools/host.d.ts.map +1 -0
- package/build/tools/host.js +13 -0
- package/build/tools/host.js.map +1 -0
- package/build/tools/iam/types.d.ts +44 -0
- package/build/tools/iam/types.d.ts.map +1 -0
- package/build/tools/iam/types.js +2 -0
- package/build/tools/iam/types.js.map +1 -0
- package/build/tools/iam.d.ts +4 -0
- package/build/tools/iam.d.ts.map +1 -0
- package/build/tools/iam.js +229 -0
- package/build/tools/iam.js.map +1 -0
- package/build/tools/index.d.ts +27 -1
- package/build/tools/index.d.ts.map +1 -1
- package/build/tools/index.js +14 -1
- package/build/tools/index.js.map +1 -1
- package/build/tools/inflight/types.d.ts +47 -0
- package/build/tools/inflight/types.d.ts.map +1 -0
- package/build/tools/inflight/types.js +2 -0
- package/build/tools/inflight/types.js.map +1 -0
- package/build/tools/inflight.d.ts +3 -0
- package/build/tools/inflight.d.ts.map +1 -0
- package/build/tools/inflight.js +88 -0
- package/build/tools/inflight.js.map +1 -0
- package/build/tools/kits/types.d.ts +12 -0
- package/build/tools/kits/types.d.ts.map +1 -0
- package/build/tools/kits/types.js +2 -0
- package/build/tools/kits/types.js.map +1 -0
- package/build/tools/kits.d.ts +4 -0
- package/build/tools/kits.d.ts.map +1 -0
- package/build/tools/kits.js +33 -0
- package/build/tools/kits.js.map +1 -0
- package/build/tools/mcp.d.ts +13 -19
- package/build/tools/mcp.d.ts.map +1 -1
- package/build/tools/mcp.js +117 -38
- package/build/tools/mcp.js.map +1 -1
- package/build/tools/platform.d.ts +14 -0
- package/build/tools/platform.d.ts.map +1 -0
- package/build/tools/platform.js +90 -0
- package/build/tools/platform.js.map +1 -0
- package/build/tools/production/types.d.ts +22 -0
- package/build/tools/production/types.d.ts.map +1 -0
- package/build/tools/production/types.js +2 -0
- package/build/tools/production/types.js.map +1 -0
- package/build/tools/production.d.ts +4 -0
- package/build/tools/production.d.ts.map +1 -0
- package/build/tools/production.js +80 -0
- package/build/tools/production.js.map +1 -0
- package/build/tools/refusal/types.d.ts +95 -0
- package/build/tools/refusal/types.d.ts.map +1 -0
- package/build/tools/refusal/types.js +2 -0
- package/build/tools/refusal/types.js.map +1 -0
- package/build/tools/refusal.d.ts +4 -0
- package/build/tools/refusal.d.ts.map +1 -0
- package/build/tools/refusal.js +663 -0
- package/build/tools/refusal.js.map +1 -0
- package/build/tools/settings/types.d.ts +42 -0
- package/build/tools/settings/types.d.ts.map +1 -0
- package/build/tools/settings/types.js +2 -0
- package/build/tools/settings/types.js.map +1 -0
- package/build/tools/settings.d.ts +4 -0
- package/build/tools/settings.d.ts.map +1 -0
- package/build/tools/settings.js +71 -0
- package/build/tools/settings.js.map +1 -0
- package/build/tools/status/types.d.ts +25 -0
- package/build/tools/status/types.d.ts.map +1 -0
- package/build/tools/status/types.js +2 -0
- package/build/tools/status/types.js.map +1 -0
- package/build/tools/status.d.ts +4 -0
- package/build/tools/status.d.ts.map +1 -0
- package/build/tools/status.js +183 -0
- package/build/tools/status.js.map +1 -0
- package/build/tools/stories/types.d.ts +48 -0
- package/build/tools/stories/types.d.ts.map +1 -0
- package/build/tools/stories/types.js +2 -0
- package/build/tools/stories/types.js.map +1 -0
- package/build/tools/stories.d.ts +8 -0
- package/build/tools/stories.d.ts.map +1 -0
- package/build/tools/stories.js +79 -0
- package/build/tools/stories.js.map +1 -0
- package/build/tools/types.d.ts +177 -26
- package/build/tools/types.d.ts.map +1 -1
- package/build/tools/types.js +1 -32
- package/build/tools/types.js.map +1 -1
- package/build/types.d.ts +391 -26
- package/build/types.d.ts.map +1 -1
- package/build/utils.d.ts +8 -0
- package/build/utils.d.ts.map +1 -0
- package/build/utils.js +12 -0
- package/build/utils.js.map +1 -0
- package/package.json +30 -34
- package/build/harness/index.d.ts +0 -28
- package/build/harness/index.d.ts.map +0 -1
- package/build/harness/index.js +0 -83
- package/build/harness/index.js.map +0 -1
- package/build/harness/templates.d.ts +0 -30
- package/build/harness/templates.d.ts.map +0 -1
- package/build/harness/templates.js +0 -154
- package/build/harness/templates.js.map +0 -1
- package/build/tools/jobs.d.ts +0 -12
- package/build/tools/jobs.d.ts.map +0 -1
- package/build/tools/jobs.js +0 -47
- package/build/tools/jobs.js.map +0 -1
|
@@ -0,0 +1,1033 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: viable-sdk
|
|
3
|
+
description: How to use @owlmeans/viable-sdk — the connector SDK an external coding agent drives the OwlMeans Viable platform with — the token-authenticated client context, the two host kinds and their tool catalogue, the session operation loop, the local slot executor and local run, the model-task envelope, and the extension seam a host adds its own tools through. Auto-invoked when building or changing a connector, an MCP host, a connector tool, the task envelope, or anything that executes platform slot commands on a developer's machine.
|
|
4
|
+
user-invocable: false
|
|
5
|
+
---
|
|
6
|
+
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
7
|
+
|
|
8
|
+
# @owlmeans/viable-sdk
|
|
9
|
+
|
|
10
|
+
**Layer:** Tooling (Node/Bun; not a browser or React package)
|
|
11
|
+
**Install:** `"@owlmeans/viable-sdk": "^0.1.18"` in `dependencies`
|
|
12
|
+
**Subpaths:** `.` · `./executor` · `./run` · `./tools` · `./task`
|
|
13
|
+
**Contracts:** `@owlmeans/viable-common` (`./connect`, `./slot`, `./integrity`, and the planning
|
|
14
|
+
vocabulary — story type and story flow) and `@owlmeans/planning` (the planning protocol tree
|
|
15
|
+
and facade) — every name on the wire is declared there, so the SDK and the platform cannot spell one
|
|
16
|
+
differently.
|
|
17
|
+
|
|
18
|
+
Everything a coding agent needs in order to drive the platform: authenticate with one access token,
|
|
19
|
+
attach a session to a project, execute the platform's slot commands against a directory on this
|
|
20
|
+
machine, deliver its model calls to the parent agent, and run the generated application locally.
|
|
21
|
+
`@owlmeans/viable-mcp` is one host built on it.
|
|
22
|
+
|
|
23
|
+
## Key Exports
|
|
24
|
+
|
|
25
|
+
| Export | Description |
|
|
26
|
+
|--------|-------------|
|
|
27
|
+
| `makeSdkContext({ apiUrl, token, service?, timeout?, llm?, onRejected? })` | A client context authenticated by one token — a literal or a thunk — with the connector protocols and the planning tree bound, the planning client registered (`timeout` is its HTTP deadline) and the call-collect transport in front of the API client (`llm: ConnectLlm.Local` turns collection on) |
|
|
28
|
+
| `createCallCollectTransport({ delegated })` → `CallCollectTransport` · `appendCallCollectTransport(context, opts)` · `CallCollectOptions` · `CollectedCall` | The context's HTTP transport (`transport:http`): request now, collect later in the delegated mode |
|
|
29
|
+
| `makeRemoteConnectorApi(context, { timeout? })` · `RemoteConnectorOptions` | `ConnectorApi` over HTTP; its `planning` is the context's planning client facade; `timeout` bounds every request but the session verbs' (one request — a collected call's hops carry their own) |
|
|
30
|
+
| `openSession(opts)` → `SessionRuntime` (`nextTask(waitMs, signal?)`, `taskAvailable(waitMs, signal?)`, …) | One attached session: the pull loop, the serial worker, the task queue, `stats` |
|
|
31
|
+
| `createInflightCalls()` → `InflightCalls` · `makeHandoverHelper(deps)` → `HandoverHelper` (`run`/`contain`/`collect`/`report`/`pending`) | The delegated mode's handover: the calls still running after their tool answered, and how a blocked tool answers with its model task |
|
|
32
|
+
| `makeTaskEnvelopeModel(task)` — `.renderTaskEnvelope({ harness })` · `.parseTaskResult(raw)` | What the parent agent is told; what its answer is checked against |
|
|
33
|
+
| `makeModelTaskDriver({ models })` · `TaskDriver` | A reference parent agent backed by a chat model (tests, CLIs) |
|
|
34
|
+
| `ToolHost.extensions` · `EXTENSION_TOOLS` | Tools the hosting process adds beyond the catalogue (the agent-setup pair `describe_harness` / `install_harness` is one such extension, shipped outside this package); listed, filtered by their own `availability` and registered like the catalogue's own |
|
|
35
|
+
| `catalogue` · `catalogueHelper` (`.visibleTools(host)` · `.toolByName`) · `registerCatalogue(server, deps)` · `serverInstructions({ host })` | The tools and how they reach an MCP server |
|
|
36
|
+
| `statusTextHelper` — `.renderProjectStatus`, `.renderStoryStatus(status, { landing? })`, `.renderPipelineStatus`, `.renderBatchStatus`, `.conversionNext` | Domain status as concise lines ending in the next valid action |
|
|
37
|
+
| `controlToolHelper` — `.stopNeedsConfirm`, `.unconfirmedStop`, `.unconfirmedCancel`, `.renderStop`, `.renderSteer`, `.renderCancel` | The agent controls' words: what a stop rolls back, what a steering message does |
|
|
38
|
+
| `storyHelper` — `.resolveStory(deps, projectId, ref)` · `.storyQuery(projectId, filter?)` · `.renderStories(items, page, total)` · `.isLandingStory(card)`; `STORY_ORDER` · `LANDING_MARK` · `LANDING_NOTE` | The story tools' reading of planning cards |
|
|
39
|
+
| `PROJECT_SETTINGS` · `settingsHelper` — `.projectSettingOf(key)` · `.settingsPatch(args)` · `.renderProjectSettings(projectId, settings)` · `.settingsReach(target)` | The project-settings tools: each setting's label and rule, the patch a call asks for, the rendering |
|
|
40
|
+
| `PLATFORM_CATALOGUE` (`pipelines`, `features`, `capabilities`) · `renderPlatform(catalogue, host)` · `GENERATED_SUMMARY` | What `describe_platform` renders, and the one-sentence product summary `describe_capabilities` ends with |
|
|
41
|
+
| `ToolHostKind` (`Stdio`/`Http`) · `ToolHost` · `ToolDeps` (`detach?`, `release?`, `inflight?` beside `attach`) · `ToolAnnotations` · `toolHostHelper` (`anyHost`/`localTarget`/`cloudTarget`/`withExecutor`/`delegatedLlm`/`sessionCapable`/`performsModelTasks`) | The host description and the availability predicates |
|
|
42
|
+
| `./executor`: `makeLocalSlotExecutor(dir, opts?)`, `createLocalFileHelper`, `createLocalShellHelper`, `makeLocalGitHelper(dir).dispatchGitCommand`, `integrityHelper` (`verifyTarget`/`forgetIntegrity`), `makeLayoutHelper(dir)` (`targetPaths`/`apiPath`/`webPath`/`workerPath`), `makeTargetEnvHelper(dir)` (`backendEnv`/`frontendEnv`), `healthHelper` (`classifyTargetHealth`/`readTargetHealth`), `runBootCheck`, `confineToProject`, `spawnHelper` | The publisher's workload, on somebody's laptop |
|
|
43
|
+
| `./run`: `makeLocalRunHelper(dir)` (`runLocal`, `stopLocal`, `localStatus`), `createLocalServer`, `makeRunStateHelper(dir)` (`startApi`/`startWorker`/`restartApi`, `readRun`/`writeRun`/`clearRun`), `runProcessHelper.stopProcess` | Building and running the generated app locally |
|
|
44
|
+
| `makeMarkerHelper(dir)` (`readMarker`/`writeMarker`/`discoverProject`/`isViableTree`) · `makeProjectEnvHelper(dir)` (`readEnv`/`writeEnv`/`envStatus`) · `dotenvHelper.replaceManagedBlock` | The `.viable/connect.json` marker and the managed `.env` block |
|
|
45
|
+
| `SdkError`, `SdkAuthError`, `SdkMisconfigured`, `SdkUnsupported` | Registered `ResilientError` classes |
|
|
46
|
+
| `ENV_TOKEN`, `ENV_API_URL`, `ENV_MCP_URL`, `ENV_TARGET`, `ENV_LLM`, `ENV_HARNESS`, `ENV_PROJECT_DIR` · `DEFAULT_MCP_URL` · `resolveMcpUrl(values)` · `TOOL_DEADLINE_MS` (45 s) · `HANDOVER_WAIT_MS` (40 s) · `COMMIT_WAIT_MS` (20 s) · `COMMIT_POLL_SEC` · `STORY_PAGE_SIZE` · `NEXT_TASK_WAIT_MS` · `PULL_WAIT_MS` | Configuration and deadlines |
|
|
47
|
+
|
|
48
|
+
## One credential, and the routes the server declares
|
|
49
|
+
|
|
50
|
+
`makeSdkContext` registers `makeTokenCarrierGuard` under `DEFAULT_GUARD` and binds the SAME
|
|
51
|
+
immutable `connectProtocols({ guard })` tree the server mounts (on the platform's public API host),
|
|
52
|
+
so a path or a schema cannot be right on one end and wrong on the other. The planning tree is bound
|
|
53
|
+
the same way: `makePlanningProtocols({ base: { alias: 'viable:manager-api:planning', path:
|
|
54
|
+
'/planning' }, guards: DEFAULT_GUARD, socketBase: <the /update base> })` — the platform's mount minus
|
|
55
|
+
its ownership gate, which is the server's to apply — followed by `appendPlanningClient(context, {
|
|
56
|
+
protocols, bind: false, poll: COMMIT_POLL_SEC, timeout: opts.timeout ?? TOOL_DEADLINE_MS, schemas: false })`. The
|
|
57
|
+
base alias and path are literals beside the `/update` base for the same reason that one is: they
|
|
58
|
+
belong to the platform, and every planning alias and path derives from them. The aliases are this
|
|
59
|
+
context's registry keys only; the wire is the path, which is why the platform may register its copy
|
|
60
|
+
under aliases of its own. No socket opener is passed, so a commit is awaited by long
|
|
61
|
+
poll only, and the schema bundle is not fetched at startup — nothing a tool does reads a flow, and a
|
|
62
|
+
server nobody has asked anything yet makes no call. There is deliberately **no second credential path**: a connector that could
|
|
63
|
+
fall back to another form of authentication is a connector whose access nobody can revoke by
|
|
64
|
+
revoking a token. A literal token that is empty or lacks `CONNECT_TOKEN_PREFIX` is refused locally
|
|
65
|
+
(`SdkMisconfigured` / `SdkAuthError`), because a 401 says nothing about which of several plausible
|
|
66
|
+
mistakes was made and the answer is always the same one.
|
|
67
|
+
|
|
68
|
+
**`token` may be a thunk** (`() => string | Promise<string>`), resolved on every request — what a
|
|
69
|
+
credential holder (`@owlmeans/cli-auth`) hands over, since it is `''` until a browser sign-in
|
|
70
|
+
completes and a real token after, with no reconfiguration. Only a literal is validated eagerly; a
|
|
71
|
+
thunk's value is unknown at construction, and an empty one is simply "not signed in". `onRejected`
|
|
72
|
+
is passed to the carrier guard and fires when the platform 401s the presented token, which is where
|
|
73
|
+
the holder forgets a dead file token (or reports a dead environment one) — see [[auth-token]].
|
|
74
|
+
|
|
75
|
+
**The `/mcp` URL is a value the SDK resolves, never one it derives:** `resolveMcpUrl(values)` returns
|
|
76
|
+
`values[ENV_MCP_URL]` (`VIABLE_MCP_URL`, empty = unset) else `DEFAULT_MCP_URL`
|
|
77
|
+
(`https://api.owlmeans.com/mcp`), trailing slash dropped so a resource URI has one spelling. The
|
|
78
|
+
caller passes the already merged environment + `~/.owlmeans` values (environment wins). A deployment's
|
|
79
|
+
own resource identifier stays host-derived on the server.
|
|
80
|
+
|
|
81
|
+
**The context must also declare the platform's `/update` base itself.** The planning commit feed
|
|
82
|
+
hangs under that namespace (the SDK never opens it), and a parent an entrypoint registry cannot
|
|
83
|
+
resolve fails the **whole context at init** rather than the one call that would have used it — a server that exited at startup
|
|
84
|
+
with `Entrypoint viable:manager-api:update:base not found`. It is declared with the path the
|
|
85
|
+
platform declares and never binds: it is a namespace, and nothing calls it.
|
|
86
|
+
|
|
87
|
+
**`cfg.webService` names the API CLIENT, never the backend.** It is the alias the entrypoint handler
|
|
88
|
+
looks up with `context.service(...)`, and the only thing registered under it is the `ApiClient` that
|
|
89
|
+
`makeClientContext` appends (`web-client`). Naming the backend service there — which is exactly what
|
|
90
|
+
`addWebService` does — makes every call fail with `Service <backend> not found` before a request is
|
|
91
|
+
sent. The backend belongs in `cfg.services` as an ordinary descriptor marked `default: true`; the
|
|
92
|
+
connect routes name no service of their own, so that is the one they resolve to. Its host is `apiUrl`,
|
|
93
|
+
the platform's PUBLIC API origin (path-less: the connector tree, planning, `/mcp`, OAuth); its alias
|
|
94
|
+
(`service`, default `viable-manager-api`) is a registry key of this context only, never on the wire.
|
|
95
|
+
|
|
96
|
+
**A POST with nothing to say still sends `{}`.** The client sets no `Content-Type` for an absent
|
|
97
|
+
body, and Fastify answers **415 Unsupported Media Type** before the handler runs — an error about
|
|
98
|
+
media types for a call whose only fault was having no arguments. `makeRemoteConnectorApi` fills an
|
|
99
|
+
empty body for any POST route that was given none; `session.close` is exactly that shape.
|
|
100
|
+
|
|
101
|
+
## Two hosts, one interface
|
|
102
|
+
|
|
103
|
+
`ConnectorApi` is an interface because two implementations must stay interchangeable — one calling
|
|
104
|
+
the API over HTTP (`makeRemoteConnectorApi`, the npx server) and one calling the platform's own
|
|
105
|
+
handlers in process (`makeInProcessConnectorApi` in `viable-public-api`, behind `POST /mcp`). Every
|
|
106
|
+
tool is written against it, so a tool cannot accidentally work in only one of the two. Which route
|
|
107
|
+
`openSession` calls fixes the mode: the delegated route carries the entitlement gate, so a caller
|
|
108
|
+
without the capability is refused at the boundary rather than by a check somewhere inside.
|
|
109
|
+
|
|
110
|
+
`ConnectorApi` has exactly one member per connector route plus the planning facade — the session
|
|
111
|
+
(`openSession`, `closeSession`, `pullOps`, `submitOp`), projects (with the agent controls
|
|
112
|
+
`project.stop(id, AgentStopArgs)` → `ConnectAgentLock`, `project.steer(id, { prompt })` → `AgentSteerResult`
|
|
113
|
+
and the story batch `project.batch.start(id, { stories? })` / `.get(id)` (`null` when none ran) /
|
|
114
|
+
`.cancel(id)` → `StoryBatchView` — only the fields named cross the wire), branding (`projectBranding(id, scope?)`,
|
|
115
|
+
`saveProjectBranding(id, patch, scope?)`, `setPlatformCredit(id, hidden, scope?)`,
|
|
116
|
+
`copyOrganizationBranding(id, scope?)` — a scope sent only when named), the configuration
|
|
117
|
+
(`config.get(id, scope?)`, `config.save(id, body, scope?)`, `config.recollect(id)`), the
|
|
118
|
+
organization's defaults (`account.branding.get()`, `.save(patch)`, `.backfill()`), the person's
|
|
119
|
+
inference preference (`account.llm.get()`, `.set(mode)`) and a project's override
|
|
120
|
+
(`project.llm(id)`, `project.setLlm(id, mode | null)` — `null` crosses as `null`), the person's
|
|
121
|
+
tokens (`account.tokens.list()`, `.revoke(id)` — never a create), consents
|
|
122
|
+
(`account.privacy.status()`, `.withdraw(keys)` — never a grant) and the intent pickup
|
|
123
|
+
(`account.intent.pickup(ref)`), story status, files
|
|
124
|
+
(`list`, `get(projectId, path)`, `save(projectId, path, content)`, `remove(projectId, path)`,
|
|
125
|
+
`meta(projectId, { kind, category? })` — the category sent only when named), the preview's
|
|
126
|
+
`sandbox` (`run`, `restart`, `stop`, `rebuild` → `ConnectSlotView`), the organization's `slot.list`,
|
|
127
|
+
pipeline state, conversion (`create`, `check`, `start(projectId, body?)`, `proceed(projectId, body)`
|
|
128
|
+
— the bodies are the route's, `confirm` included —, `status`, `purge`) and inquiry answers. There is
|
|
129
|
+
no capability view, no session read or heartbeat, no conversion cancel, no token create and no
|
|
130
|
+
consent grant. A member is added together with the route in `connectProtocols` and its
|
|
131
|
+
`connectRef` entry — never one without the others. The one exception is `call.collect`: it belongs to
|
|
132
|
+
the context's transport (below), which collects a delegated write's outcome under whichever member
|
|
133
|
+
made it.
|
|
134
|
+
|
|
135
|
+
## A SESSION belongs to a host that stays; `toolHostHelper.sessionCapable` is what says so
|
|
136
|
+
|
|
137
|
+
A session is a connector ATTACHED — a process that drains the project's operations and holds a model
|
|
138
|
+
task until its answer comes back. The URL-configured host answers one request and forgets, so
|
|
139
|
+
opening one there would claim the project's single connector slot, **supersede the stdio connector
|
|
140
|
+
legitimately holding it**, and be abandoned before the first operation was delivered.
|
|
141
|
+
|
|
142
|
+
So `ensureSession` — what `confirm_project`, `reinitialize_project`, `develop_story`, `start_story_batch`,
|
|
143
|
+
`steer_agent`, `reset_story`,
|
|
144
|
+
`complete_story`,
|
|
145
|
+
`modify_project`, `rename_project` and `resume_pipeline` call before returning their job, every
|
|
146
|
+
story write, `update_project_settings`, `set_platform_credit`, `update_project_configuration`,
|
|
147
|
+
`recollect_configuration`, `apply_planning_kit`, `write_file` (its content check is a
|
|
148
|
+
model call the delegating session performs) and the conversion verbs before their
|
|
149
|
+
write, and `answer_question` in the delegated mode before an answer by id resumes a run — opens one
|
|
150
|
+
only on a `toolHostHelper.sessionCapable` host. `next_task`/`submit_task_result` are available on
|
|
151
|
+
`.performsModelTasks` (delegated **and** session-capable) and nowhere else: in the cloud mode the
|
|
152
|
+
platform performs every model call itself, a conversion's included, so there is never a task to
|
|
153
|
+
collect. `next_question`/`answer_question` stay on `.sessionCapable` — a person is asked whoever
|
|
154
|
+
performs the models. `serverInstructions` reads the same predicates: a parent told to call
|
|
155
|
+
`next_task` when the tool is not in its list is a parent that waits for a run nobody will advance.
|
|
156
|
+
A host that cannot serve the delegated mode says so in one sentence instead, naming the stdio
|
|
157
|
+
connector.
|
|
158
|
+
|
|
159
|
+
The in-process implementation refuses the four session verbs (`openSession`, `closeSession`,
|
|
160
|
+
`pullOps`, `submitOp`) with a message naming `@owlmeans/viable-mcp`, because "unsupported" alone
|
|
161
|
+
leaves the reader with nothing to do about it.
|
|
162
|
+
|
|
163
|
+
## A tool that cannot work in a mode is HIDDEN, not failing
|
|
164
|
+
|
|
165
|
+
`ToolDefinition.availability` and `catalogueHelper.visibleTools(host)` decide the catalogue a parent actually sees. A
|
|
166
|
+
tool a host cannot serve is a tool the parent tries once, is refused, and remembers as broken — so
|
|
167
|
+
the list it reads is exactly the set of things that work for it. Predicates (`toolHostHelper`): `anyHost`,
|
|
168
|
+
`localTarget`, `cloudTarget`, `withExecutor`, `delegatedLlm`, `sessionCapable`, `performsModelTasks`.
|
|
169
|
+
|
|
170
|
+
Every catalogue tool but `describe_platform` belongs to EXACTLY ONE `PLATFORM_CATALOGUE.capabilities`
|
|
171
|
+
group, and every group carries an `absent` sentence rendered where a host hides it. `serverInstructions`
|
|
172
|
+
is the compact map a parent reads first: the workflow, ONE line naming each capability family by a few
|
|
173
|
+
of its tools ("everything the web application does except billing"), the cursor rule of the feeds, the
|
|
174
|
+
`confirm: true` set, the browser-only acts (payments and the plan, token creation, giving a marketing
|
|
175
|
+
consent or accepting terms, approving a connector, finishing a GitHub authorization) and the mode
|
|
176
|
+
rules — and it names NO tool the host hides: the files/preview, git/GitHub and production families and
|
|
177
|
+
`file_changes` appear only for a cloud target, the local block says those are the machine's.
|
|
178
|
+
|
|
179
|
+
## The delegated mode: every model call is the parent's, and a blocked tool hands its task over
|
|
180
|
+
|
|
181
|
+
`llm=local` is the ONE switch for who performs the platform's model calls. In the delegated mode
|
|
182
|
+
every one of them is this session's parent's — project drafting, every content check, story
|
|
183
|
+
formatting, every run's calls and a conversion's; in the cloud mode none is. The wording says it in
|
|
184
|
+
those terms everywhere it is said (`serverInstructions`, `describe_capabilities`, `describe_platform`,
|
|
185
|
+
the `model-tasks` group, `CONVERSION_COST`, `WORKING_RULE`): all or none, nothing in between.
|
|
186
|
+
|
|
187
|
+
A platform call can therefore stop mid-way for a model call the parent itself must perform — the
|
|
188
|
+
very parent blocked on that tool. `registerCatalogue` answers that with the HANDOVER
|
|
189
|
+
(`makeHandoverHelper(deps).run`) for every tool except the task/question loop and the tools that
|
|
190
|
+
never reach the platform (`HANDOVER_EXEMPT`):
|
|
191
|
+
|
|
192
|
+
- The call starts and races (i) finishing, (ii) a model task becoming available in the CURRENT
|
|
193
|
+
session's queue (`taskAvailable`, looked for in 1 s slices because a delegated create opens its
|
|
194
|
+
session from inside the call), (iii) `HANDOVER_WAIT_MS`. A finished call answers as it always did.
|
|
195
|
+
- A task first: it is taken (`nextTask(0)`; null means another caller was quicker and the race goes
|
|
196
|
+
on) and the tool answers "`<tool>` is NOT finished — the platform is waiting on a model call you
|
|
197
|
+
must perform. Do not call `<tool>` again. Run the task below in a clean subagent at LOW reasoning
|
|
198
|
+
effort, then call submit_task_result {taskId, result}; its reply is `<tool>`'s result, or the next
|
|
199
|
+
model call it waits on." followed by the task exactly as `next_task` renders it.
|
|
200
|
+
- Neither: "still running on the platform — call next_task (or read `<status tool>`)".
|
|
201
|
+
- The call is never abandoned. It lives in `ToolDeps.inflight` (`createInflightCalls()`, held beside
|
|
202
|
+
the server — never on the session, which reopens whenever the connector moves), keyed by tool name
|
|
203
|
+
+ canonical JSON args, so an identical repeat JOINS it. A call some tool still waits on is that
|
|
204
|
+
tool's to answer; once nobody waits it is detached, and `submit_task_result` and `next_task`,
|
|
205
|
+
after their own work, race any detached call settling, the next task and the wait, then report
|
|
206
|
+
every settled call (its text phrased exactly as the wrapper's catch phrases it; each in
|
|
207
|
+
`structuredContent.settled[]`, a refusal marked `isError` there and never on the submit itself).
|
|
208
|
+
- The cloud mode and the URL host keep the plain deadline path, unchanged.
|
|
209
|
+
|
|
210
|
+
**A create names an UNATTACHED session.** In the delegated mode `create_project` and
|
|
211
|
+
`convert_project`'s create path first open a session with no project (`ToolDeps.detach?()` when one
|
|
212
|
+
is attached, then `deps.session()`), send its id as `sessionId` (`ConnectorApi.project.create(prompt,
|
|
213
|
+
target?, sessionId?)`, `ConnectConvertCreateBody.sessionId`) — the platform hands the checks it runs
|
|
214
|
+
before the card exists to that session's parent — and once the platform answers with project P,
|
|
215
|
+
`deps.attach(P); ensureSession(deps, P)`. A host without `detach` sends none while attached
|
|
216
|
+
elsewhere. The cloud mode sends none and opens nothing.
|
|
217
|
+
|
|
218
|
+
**Request now, collect later.** A delegated write may wait on a model call this very parent
|
|
219
|
+
performs — create, convert create, confirm, reinit, modify, rename, a settings save, a story create or
|
|
220
|
+
update (the platform formats the narrative in its planning `execute`) — for longer than an edge holds
|
|
221
|
+
a response open (~100 s). So `makeSdkContext({ llm: ConnectLlm.Local })` registers ONE transport
|
|
222
|
+
(`createCallCollectTransport`, under `transportAlias('http')`, which every client entrypoint asks for
|
|
223
|
+
before the API client) in front of the context's API client — the connector routes and the planning
|
|
224
|
+
client both go through it, with no per-method code:
|
|
225
|
+
|
|
226
|
+
- every NON-GET request carries `x-viable-call: <randomUUID()>` (`CONNECT_CALL_HEADER`); a request
|
|
227
|
+
that already names one keeps it, so a retry of that request is the same call to the platform;
|
|
228
|
+
- an answer `{ pending: <that id> }` (202, after the platform's `CONNECT_CALL_ACCEPT_MS` hold) becomes
|
|
229
|
+
`connect.call.collect` long polls (`wait` = `CONNECT_CALL_COLLECT_WAIT_SEC`, HTTP deadline wait +
|
|
230
|
+
10 s), repeated while `pending` (an unheld one — under 1 s — rests 1 s first) until `settled`: its
|
|
231
|
+
`value` and `outcome` resolve the original call exactly as a direct answer would have (the planning
|
|
232
|
+
facade still builds its receipt), its `error` is `ResilientError.ensure`d back into its own class;
|
|
233
|
+
`lost` throws `ConnectCallLost` (phrased in `REFUSALS`: the outcome was lost, read the matching
|
|
234
|
+
status tool before repeating);
|
|
235
|
+
- a hop the LINE dropped (a transient transport error, axios's own timeout, an edge 502/503/504) is
|
|
236
|
+
asked again, up to `COLLECT_DROPPED_HOPS` in a row; any other answer — a direct value, a `pending`
|
|
237
|
+
of another id, a refusal, every GET, the cloud mode — passes through untouched.
|
|
238
|
+
|
|
239
|
+
`collect` is the transport's route, not a `ConnectorApi` member. Every request and collect hop stays
|
|
240
|
+
under `TOOL_DEADLINE_MS`; nothing caps the call as a whole but the platform's own model-task deadline,
|
|
241
|
+
and the handover is unchanged — its in-flight promise simply lasts as long as the collection.
|
|
242
|
+
|
|
243
|
+
## 45 seconds is the ceiling, so long operations return domain status
|
|
244
|
+
|
|
245
|
+
Every MCP host bounds a tool call and the strictest default in the field is sixty seconds (Codex).
|
|
246
|
+
`TOOL_DEADLINE_MS` leaves room for the round trip and keeps the connector inside every host's
|
|
247
|
+
ceiling without configuration. A tool that outlives its host's ceiling is reported to the user as a
|
|
248
|
+
**broken server**, and the true answer — the platform was slow — never reaches them.
|
|
249
|
+
|
|
250
|
+
Anything that takes minutes returns the owning domain's status and continues server-side.
|
|
251
|
+
`project_status`, `story_status`, `conversion_status` and `pipeline_status` compose the durable
|
|
252
|
+
records and run state a parent needs. `registerCatalogue` enforces the deadline per call (the
|
|
253
|
+
delegated mode's handover answers within `HANDOVER_WAIT_MS` instead) and
|
|
254
|
+
converts a throw into an `isError` result the model can read and act on, because an exception
|
|
255
|
+
crossing the transport tells it only that something went wrong somewhere. Each status renderer
|
|
256
|
+
ends with a single `next:` line so the parent follows the domain workflow and does not invent a
|
|
257
|
+
polling strategy or treat a parked run as failed. What HAPPENED along the way is read from the cursor feeds
|
|
258
|
+
(`project_activity`, below) — a feed is never the answer to where a run stands.
|
|
259
|
+
|
|
260
|
+
## The story tools speak planning
|
|
261
|
+
|
|
262
|
+
A user story is a planning CARD of `VIABLE_STORY_TYPE` under its project card, and every change to one
|
|
263
|
+
is a transition executed through `ConnectorApi.planning` — a `PlanningFacade`: the client facade
|
|
264
|
+
`appendPlanningClient` registers over HTTP, and `ensurePlanningService(ctx).for(scope)` in the
|
|
265
|
+
platform's in-process host. The scope a call answers for is the CREDENTIAL's; a tool sends none.
|
|
266
|
+
|
|
267
|
+
| Tool | Facade call |
|
|
268
|
+
|---|---|
|
|
269
|
+
| `list_stories` | `cards.list({ ...storyHelper.storyQuery(project, { status, area }), page, size, sort: STORY_ORDER })` → `storyHelper.renderStories` |
|
|
270
|
+
| `search_stories` | the same with `{ q }` |
|
|
271
|
+
| `create_story` | `execute({ action: create, card: { kind: card, type: VIABLE_STORY_TYPE, parent, title, fields: { primary: false } } }, { wait: true, timeout: COMMIT_WAIT_MS })` |
|
|
272
|
+
| `update_story` | `storyHelper.resolveStory` → `execute({ card, action: update, changes: { title }, expectSeq: head ?? seq }, { wait: true, … })` |
|
|
273
|
+
| `delete_story` | `storyHelper.resolveStory` → `execute({ card, action: delete }, { wait: true, … })` → the project-lock poll |
|
|
274
|
+
| `develop_story` | `storyHelper.transit(deps, project, storyId, start, 'connector:develop-story')` |
|
|
275
|
+
| `reset_story` | `storyHelper.transit(…, reset, 'connector:reset-story')` — any status back to planned |
|
|
276
|
+
| `complete_story` | `storyHelper.transit(…, complete, 'connector:complete-story')` — in progress only |
|
|
277
|
+
| `story_status` | `storyHelper.resolveStory` → the card, its development run, pending inquiry, warning and landing mark |
|
|
278
|
+
| `start_story_batch` | `ensureSession` → each `storyIds` ref `storyHelper.resolveStory`d to its card id (deduplicated; none = every planned story, `{}`) → `api.project.batch.start` → `statusTextHelper.renderBatchStatus` |
|
|
279
|
+
| `story_batch_status` | `api.project.batch.get` → the batch in implementation order (code, state, ordering reason), progress, halt reason phrased, `next:`; `null` → "No story batch has run" |
|
|
280
|
+
| `cancel_story_batch` | `confirm: true` → `api.project.batch.cancel` FIRST, then — when `current` has no outcome — `api.project.stop({ scope: story, storyId: current, confirm: true })`; `AgentNotRunning` there means the batch was between stories |
|
|
281
|
+
|
|
282
|
+
Rules the table rests on:
|
|
283
|
+
|
|
284
|
+
- **A tool NAME is a parent agent's vocabulary and is never renamed**, and neither is an argument:
|
|
285
|
+
`storyId` stays `storyId` although the platform keys on card ids. New facts are added (`designSystem`
|
|
286
|
+
on `confirm_project`, the project status line and the design-system section of `project_status`),
|
|
287
|
+
never substituted.
|
|
288
|
+
- **`storyId` accepts a code or an id**, because `list_stories` prints the CODE — the handle every
|
|
289
|
+
generated file names a story by. `storyHelper.resolveStory` asks for both at once, the id wins, a code is also
|
|
290
|
+
tried uppercased, and neither answers for a story of another project (`ProjectStoryNotFound`).
|
|
291
|
+
- **A story moves through its flow by ONE helper.** `storyHelper.transit(deps, projectId, ref,
|
|
292
|
+
transition, cause)` resolves the story, executes the transition awaited to its commit and answers
|
|
293
|
+
`{ card, status }` from `story.status`. Development is the story's `start`, not a call of its own:
|
|
294
|
+
the platform begins the run once that move COMMITS, and refuses it there too (the flow, one story
|
|
295
|
+
in progress, the balance). `reset_story` (`* → planned`, the manual recovery — the code generated
|
|
296
|
+
for it stays) and `complete_story` (`in-progress → completed`, nothing generated or stopped) are
|
|
297
|
+
the browser's two manual moves. The wait is `COMMIT_WAIT_MS`, well inside the tool deadline; a late
|
|
298
|
+
commit is not a failure — the transition is durable — so the tool answers from `story_status`
|
|
299
|
+
rather than outliving the host's ceiling. A refusal is thrown, and no status is read to hide it.
|
|
300
|
+
- **A story a person writes goes as written, with no area.** Re-formatting the narrative and deciding
|
|
301
|
+
the area are the platform's, done in its planning middleware for the `connect` channel; a connector
|
|
302
|
+
that guessed an area would be a second answer. `update_story` carries only `title` and the head it
|
|
303
|
+
read, so a change made in between is refused (`WorkcardConflict`) rather than overwritten.
|
|
304
|
+
- **The delete commit is no proof the slot is done.** It says the card is gone and nothing about the
|
|
305
|
+
placeholder screens still being retired under the project lock, so `delete_story` keeps its two
|
|
306
|
+
unlocked observations.
|
|
307
|
+
- **Stories are read in `order`, then `createdAt`**: `order` is the analysis's flow ordinal (a
|
|
308
|
+
connective story sits at a fraction between two steps). `storyHelper.renderStories` keeps the line shape a parent
|
|
309
|
+
already reads — `code · status[ · primary][ · landing gate][ · area]` over the narrative — under a
|
|
310
|
+
header counting the page by intrinsic state. A new fact is a new FLAG beside `primary`; the area
|
|
311
|
+
stays last.
|
|
312
|
+
- **The landing gate story is read off the card the tool already holds** (`fields.landing`, at most
|
|
313
|
+
one per project, decided by the platform at initialization — a connector never sets it). The list
|
|
314
|
+
flags it; `story_status` and `develop_story` pass the resolved card to `statusTextHelper.renderStoryStatus`, which
|
|
315
|
+
adds `LANDING_NOTE` under the status line, and their structured result gains `landing: true`. No
|
|
316
|
+
second call and no change to `connect.story.status`: that route carries the run, not the card's
|
|
317
|
+
fields. It is said because developing that story also replaces the guest-home sketch with the real
|
|
318
|
+
component, which the narrative alone never tells a parent.
|
|
319
|
+
- **A planning refusal is phrased like any other**: `planning:illegal-transition:`,
|
|
320
|
+
`workcard-conflict:`, `workcard-not-found:`, `fields-invalid:`, `commit-timeout:`, `commit-failed:`
|
|
321
|
+
and the story markers (`viable-project:story:not-found:` / `:missconfigured:`) each have a sentence in
|
|
322
|
+
`REFUSALS`.
|
|
323
|
+
|
|
324
|
+
- **A sign-in refusal is phrased**: `oauth:sign-in-required:` (detail `<url> <code>`, the code absent
|
|
325
|
+
while no device sign-in is pending), `oauth:token-rejected:` (an environment token that was refused
|
|
326
|
+
is never replaced) and `api:auth:guard:auth-token` (a file token the platform refused — forgotten,
|
|
327
|
+
the next call signs in). Each ends with "call this tool again".
|
|
328
|
+
|
|
329
|
+
- **Run all, Stop and Steer are the browser's own controls.** A batch is the PROJECT's (no story
|
|
330
|
+
route), started over planned stories only, ordered by the platform before its first story, and
|
|
331
|
+
each of its stories runs exactly as `develop_story`'s would; `start_story_batch` is never a near-twin
|
|
332
|
+
of `develop_story` in its description. `stop_agent { scope?: run | story | batch, storyId?, confirm? }`
|
|
333
|
+
sends only what it was given (a `storyId` resolved to its card): a story stop rolls the story back to
|
|
334
|
+
where that run started and returns it to planned, `batch` also cancels the batch, `run` (an open-ended
|
|
335
|
+
change) stops once the model's current answer arrives and keeps its edits — so every scope but `run`
|
|
336
|
+
(an omitted scope included) needs `confirm: true`. A stop opens no session; `steer_agent { prompt }`
|
|
337
|
+
does (`ensureSession`: its content check is a model call) and answers the queued message id.
|
|
338
|
+
`AgentNotRunning`, `StoryBatchActive` and `StoryBatchEmpty` are phrased in `REFUSALS` by their
|
|
339
|
+
`viable-common` markers ("Nothing is running on this project right now …"). `project_status` says
|
|
340
|
+
`agent: stopping (…)` while `agent.stopping`, and names `steer_agent` / `stop_agent` while
|
|
341
|
+
`agent.steerable`.
|
|
342
|
+
|
|
343
|
+
## An irreversible tool asks for `confirm: true`, and every tool declares its annotations
|
|
344
|
+
|
|
345
|
+
`delete_project`, `unlock_project_agent`, `delete_file`, `purge_origin`, `git_discard`, `git_revert`,
|
|
346
|
+
`disconnect_github`, `revoke_access_token`, `publish_production` and `cancel_story_batch` take `confirm` (`z.boolean()`,
|
|
347
|
+
described, NO default), and `manage_app_user`'s `remove`, `manage_app_group`'s `delete` and every
|
|
348
|
+
`stop_agent` scope but `run` take it as an optional field the action requires; each answers "Not done … ask the user" without a single platform
|
|
349
|
+
call unless it is `true` — the confirmation is the TOOL's, a person agreed before the route is reached.
|
|
350
|
+
`serverInstructions` names the same set in one sentence. `delete_project` also
|
|
351
|
+
REQUIRES `projectId` (`z.string().min(1)`, never the attached project by default) and, on a
|
|
352
|
+
session-capable host attached to that project, closes the session first (`deps.release`, else
|
|
353
|
+
`currentSession()?.close()`) and `deps.detach()`es, then `api.project.destroy` → `{ id, name, alias }`;
|
|
354
|
+
on a local target it says the files and `.viable/` stay. `unlock_project_agent` (`api.project.unlock`
|
|
355
|
+
→ `{ locked, task?, lockedAt? }`) defaults to the attached project and says when a run took the lock
|
|
356
|
+
straight back.
|
|
357
|
+
|
|
358
|
+
`ToolDefinition.annotations` is REQUIRED — the MCP tool annotations a host shows the person approving
|
|
359
|
+
a call — and `registerCatalogue` passes it through beside the title, description and input schema.
|
|
360
|
+
The shapes live in `tools/consts.local.ts`: `READ_ONLY` (reads, statuses, descriptions), `WRITE`
|
|
361
|
+
(additive changes, the task and question loop), `IDEMPOTENT_WRITE` (setting a value: settings,
|
|
362
|
+
`update_story`, `reset_story`, `attach_project`, `stop_local`, `set_local_service`) and
|
|
363
|
+
`DESTRUCTIVE` (`delete_project`, `delete_story`, `purge_origin`, `reinitialize_project`, `git_revert`,
|
|
364
|
+
`cancel_story_batch` and `stop_agent` (both idempotent — a story stop rolls code back),
|
|
365
|
+
`publish_production` (it replaces the live site), and — also idempotent — `unlock_project_agent`,
|
|
366
|
+
`write_file` (it replaces a file whole), `delete_file`, `git_discard`, `disconnect_github`,
|
|
367
|
+
`revoke_access_token` and `production_control` (it takes the live site down or restarts it); `preview_control` and
|
|
368
|
+
`link_github_origin` are `IDEMPOTENT_WRITE`); `openWorldHint` is `false` everywhere except
|
|
369
|
+
`convert_project`, which may clone a repository from the internet. A new tool picks one; a read is
|
|
370
|
+
never destructive (`catalogue.spec.ts` pins the destructive set).
|
|
371
|
+
|
|
372
|
+
## A cloud target's files and preview are the web editor's and sandbox card's own calls
|
|
373
|
+
|
|
374
|
+
All cloud-target only (`toolHostHelper.cloudTarget`), on both hosts; a local project's tree and
|
|
375
|
+
process are on the user's machine, read and run directly.
|
|
376
|
+
|
|
377
|
+
- **`list_files { kind?, category? }`** — no kind (or `sources`): `files.list`, the generated
|
|
378
|
+
sources; `stories` / `meta` / `all`: `files.meta` (the story documents, the specifications beside
|
|
379
|
+
the sources narrowed by `category` — sent with `meta` only — or every metadata document).
|
|
380
|
+
- **`read_file { path }`** answers the content as the text, cut at `FILE_READ_CAP` (100 000
|
|
381
|
+
characters) with a note naming the full length; `structured` carries `path`, `length`, never the
|
|
382
|
+
content twice.
|
|
383
|
+
- **`write_file { path, content }`** replaces the whole file; **`delete_file { path, confirm }`**
|
|
384
|
+
refuses without `confirm: true` and makes no call. Both answer "the preview was rebuilt" or,
|
|
385
|
+
when `buildWarning` came back, that the rebuild FAILED and the last good build still serves — the
|
|
386
|
+
change stands either way. A protected file (manifests, build configuration) is refused by the
|
|
387
|
+
platform as `target-integrity:<path> … cannot be edited`, phrased as that one file
|
|
388
|
+
(`PROTECTED_FILE_DETAIL`), never as the tree failing its shape check.
|
|
389
|
+
- **`preview_control { action: start | restart | stop | rebuild }`** → `sandbox.run` / `restart` /
|
|
390
|
+
`stop` / `rebuild`, answered with the workload line (`statusTextHelper.renderSlot`); a rebuild
|
|
391
|
+
goes on after the answer under the project lock. Never the production site.
|
|
392
|
+
- **`list_slots`** (any host, any target) — every workload of the organization, one line each plus
|
|
393
|
+
its warnings. `viable-slot:not-found:` is phrased as a project with no workload yet.
|
|
394
|
+
|
|
395
|
+
## A cloud target's git and its GitHub connection are the web Git dialog's own calls
|
|
396
|
+
|
|
397
|
+
All cloud-target only (`toolHostHelper.cloudTarget`), on both hosts — a local project's repository is
|
|
398
|
+
on the user's machine; group `git` in `PLATFORM_CATALOGUE.capabilities`; rendered by `gitToolHelper`
|
|
399
|
+
(`tools/git.ts`, words in `consts.local.ts`: `GITHUB_STATUS_WORDS`, `GIT_SYNC_WORDS`). Members
|
|
400
|
+
`ConnectorApi.git.{status,log,commit,discard,revert}` and `ConnectorApi.github.{authorize,publish,push,
|
|
401
|
+
pull,disconnect,repos,branches,link}` (`connect.git.*` / `connect.github.*`, the library's mirror types
|
|
402
|
+
`ConnectGit*` / `ConnectGithub*` — no `@owlmeans/git` dependency, statuses are strings).
|
|
403
|
+
|
|
404
|
+
- **`git_status`** (read-only): the connection line (login → repository · status words) and the
|
|
405
|
+
tree — branch, head, ahead/behind, the first `GIT_FILES_SHOWN` changed paths. `git: null` reads as
|
|
406
|
+
"the preview is not ready" with `preview_control { action: start }` as the cure — never as an error.
|
|
407
|
+
- **`git_history`** (read-only), **`git_commit { message }`** (`WRITE`, trimmed, ≤ 200),
|
|
408
|
+
**`git_discard { confirm }`** and **`git_revert { hash, confirm }`** (`DESTRUCTIVE`; refused without
|
|
409
|
+
`confirm: true` before any call; the hash is lower-cased and must match `^[0-9a-f]{7,40}$`; a revert
|
|
410
|
+
is a NEW commit and its `dbWarning` is printed).
|
|
411
|
+
- **`connect_github`** answers the `authorizeUrl` the USER opens in the browser where they are signed
|
|
412
|
+
in; GitHub returns to the OwlMeans web application, which completes it. No tool, route or member
|
|
413
|
+
completes an authorization (`git-tools.spec.ts` pins it), and nothing ever carries the token.
|
|
414
|
+
- **`publish_to_github { repoName?, private? } | { owner, repo }`** (`WRITE`, in `answering`: the
|
|
415
|
+
`conversion:publish-origin` refusal is the tool's own sentence); half an existing repository is
|
|
416
|
+
refused locally. **`github_sync { direction: push | pull }`** answers a refused outcome
|
|
417
|
+
(`conflict`, `rejected`, `no-remote`, `auth-failed`) as `isError` with the cure; a pull rebuilds.
|
|
418
|
+
- **`github_repositories { owner?, repo?, page?, search? }`** (read-only): repositories, or — with
|
|
419
|
+
both `owner` and `repo` — that repository's branches; one of the two alone is refused locally.
|
|
420
|
+
**`link_github_origin { owner, repo, branch? }`** (`IDEMPOTENT_WRITE`) records where an import comes
|
|
421
|
+
FROM. **`disconnect_github { confirm }`** (`DESTRUCTIVE`, idempotent).
|
|
422
|
+
- The writes map to `git_status` for the handover's domain status (`STATUS_TOOL_OF`). The refusals
|
|
423
|
+
`github:not-connected`, `github:not-published` and `oauth:invalid-or-expired-state` name
|
|
424
|
+
`connect_github` / `publish_to_github`; `git-busy:` is the agent holding the project.
|
|
425
|
+
- Specs: `tests/git-tools.spec.ts`, `remote.spec.ts` (the thirteen routes and their closed bodies).
|
|
426
|
+
|
|
427
|
+
## A cloud target's production site is the web Publish dialog's own calls
|
|
428
|
+
|
|
429
|
+
All cloud-target only (`toolHostHelper.cloudTarget`), on both hosts — a local project is deployed from
|
|
430
|
+
its own machine; group `production` in `PLATFORM_CATALOGUE.capabilities`; rendered by
|
|
431
|
+
`productionToolHelper` (`tools/production.ts`, words in `consts.local.ts`: `PRODUCTION_STATUS_WORDS`,
|
|
432
|
+
`DOMAIN_STATUS_WORDS`, the actions `PRODUCTION_ACTIONS`, `DOMAIN_ACTIONS`). Members
|
|
433
|
+
`ConnectorApi.production.{status,publish,restart,stop,domain.{attach,verify,detach},auth,setRedirects}`
|
|
434
|
+
(`connect.production.*`). Never the preview — that is `preview_control`.
|
|
435
|
+
|
|
436
|
+
- **`production_status`** (read-only): never published, or the status words and — when live — the
|
|
437
|
+
address (the custom domain once `linked`, else the generated host), the warnings, the domain line,
|
|
438
|
+
and the next step.
|
|
439
|
+
- **`publish_production { confirm }`** (`DESTRUCTIVE`; refused without `confirm: true` before any
|
|
440
|
+
call) — the publish starts and goes on after the answer; **`production_control { action: restart |
|
|
441
|
+
stop }`** (`DESTRUCTIVE`, idempotent). Both in `answering`: the published-sites `limit-exhausted:`
|
|
442
|
+
refusal (a stopped site's restart takes a unit too) is the tool's own sentence.
|
|
443
|
+
- **`custom_domain { action: attach | verify | detach, domain? }`** (`WRITE`, in `answering`): an attach
|
|
444
|
+
needs `domain` (trimmed, lower-cased) and answers the two DNS records the USER creates
|
|
445
|
+
(`CNAME <domain> → <cnameTarget>`, `CNAME <dcvName> → <dcvValue>`) with the provider's state of each,
|
|
446
|
+
until the domain is `linked`; `null` from an attach is "publish first". `viable-domain:taken:` and
|
|
447
|
+
`capability-required:feature:domain--custom` are phrased.
|
|
448
|
+
- **`production_auth`** (read-only) prints the issuer, the client id and the redirect addresses — the
|
|
449
|
+
client secret only as set or not (`secretSet`; the user reads it in the web application).
|
|
450
|
+
**`set_production_redirects { redirects }`** (`IDEMPOTENT_WRITE`) replaces the whole list, applied on
|
|
451
|
+
the next publish.
|
|
452
|
+
- The writes map to `production_status` for the handover (`STATUS_TOOL_OF`).
|
|
453
|
+
- Specs: `tests/production-tools.spec.ts`, `remote.spec.ts` (the nine routes and their closed bodies).
|
|
454
|
+
|
|
455
|
+
## The generated app's sign-in is the owner console's own calls
|
|
456
|
+
|
|
457
|
+
Any host and any target (`toolHostHelper.anyHost`) — the IAM is the PLATFORM's, a local project's app
|
|
458
|
+
included; group `app-sign-in` in `PLATFORM_CATALOGUE.capabilities`; checked and rendered by
|
|
459
|
+
`iamToolHelper` (`tools/iam.ts`: `missing`, `grantBody`, `render*`; actions `APP_USER_ACTIONS`,
|
|
460
|
+
`APP_GRANT_ACTIONS`, `APP_ORGANIZATION_ACTIONS`, `APP_GROUP_ACTIONS`, `IAM_ROWS_SHOWN`, `IAM_EXTERNAL`
|
|
461
|
+
in `consts.local.ts`). Members `ConnectorApi.iam.{organizationUsers, permissions, setDefault,
|
|
462
|
+
grants.*, users.*, organizations.*, groups.*}` over `connect.iam.*` and `connect.account.iam.users`;
|
|
463
|
+
the bodies and answers are the library's owner-console types (`Iam*`, `@owlmeans/viable-common`). Every
|
|
464
|
+
tool takes `scope` (`ephemeral` | `production`, absent = the preview's client): a read sends it in the
|
|
465
|
+
query, a write — a removal included — in its body.
|
|
466
|
+
|
|
467
|
+
- Reads (`READ_ONLY`): **`app_users { projectId?, scope?, all? }`** (`all: true` = every end user of
|
|
468
|
+
every app of the organization, read-only), **`app_permissions`** (definitions + tenancy flags),
|
|
469
|
+
**`app_grants { profileId? | group? + entitySlug }`**, **`app_organizations { entitySlug? }`** (members
|
|
470
|
+
of one when a slug is named), **`app_groups { entitySlug, group? }`** (members of one when a key is named).
|
|
471
|
+
- Writes: **`set_app_permission_default { permission, defaultClass?, entityScoped? }`**
|
|
472
|
+
(`IDEMPOTENT_WRITE`); `DESTRUCTIVE`: **`manage_app_user { action: invite | update | remove }`**,
|
|
473
|
+
**`manage_app_grant { action: assign | revoke, permission, profileId | group + entitySlug,
|
|
474
|
+
resources?, mode? }`**, **`manage_app_organization { action: rename | add-member | update-member |
|
|
475
|
+
remove-member, entitySlug }`**, **`manage_app_group { action: create | update | delete | add-members |
|
|
476
|
+
remove-members, entitySlug, group }`**.
|
|
477
|
+
- **Grants restrict as well as grant.** Every subject (person or group) has its OWN blanket value per
|
|
478
|
+
binding: granted, restricted, or absent (inherits). A whole-permission revoke (`blanket`/`all`, or no
|
|
479
|
+
mode without `resources`) RESTRICTS a declared permission — the subject also loses what it inherits
|
|
480
|
+
from a default class, a group or a bundle; `mode: inherit` (revoke-only) takes back only the
|
|
481
|
+
subject's own value, so it holds what it inherits again; a `resources` revoke removes those ids only.
|
|
482
|
+
`iamToolHelper.renderRevoke` words the result by that mode. The tool descriptions and the `mode`
|
|
483
|
+
enum text say the same, and nothing here re-implements the IAM's evaluation.
|
|
484
|
+
- `app_grants` prints what is HELD and what a subject's own restriction withholds (`restricted: true`
|
|
485
|
+
— listed, never held): the head counts them apart (`Grants (N held, M restricted)`), a restricted row
|
|
486
|
+
ends `· restricted`, and `IAM_RESTRICTED_NOTE` closes a listing that has any. Origins print as
|
|
487
|
+
given: `direct`, `default`, `group` (with `through`), `bundle` (a group's own listing: the names its
|
|
488
|
+
bundles select).
|
|
489
|
+
- The platform's staff group (`IAM_MEMBERS_GROUP`, `managed: true`) keeps its members, bundles and
|
|
490
|
+
existence: `manage_app_group` writes to them are refused by the IAM; its PERMISSIONS are the owner's,
|
|
491
|
+
granted and restricted with `manage_app_grant { group }`. `renderGroup` and the `manage_app_group`
|
|
492
|
+
description say exactly that — never "read-only".
|
|
493
|
+
- **Action-specific fields are checked BEFORE any call** (`iamToolHelper.missing`): an invite needs
|
|
494
|
+
`email`, an update something to change, a grant exactly ONE subject (a person, or a group with its
|
|
495
|
+
organization's `entitySlug` — never both), an `assign` no revoke-only mode (`REVOKE_ONLY_GRANT_MODES`:
|
|
496
|
+
`all`, `inherit`), a group's update `title` or the WHOLE `bundles` list, the members' moves
|
|
497
|
+
`profileIds`; **`remove` and `delete` need `confirm: true`**. A refusal is `isError` with nothing
|
|
498
|
+
called. The contract's schema admits every mode on both bodies; the IAM refuses the revoke-only
|
|
499
|
+
ones on a grant.
|
|
500
|
+
- A listing answered `external: true` prints `IAM_EXTERNAL` (the app's sign-in is managed in a console
|
|
501
|
+
of its own), never "nobody". `refusalHelper` phrases `iam:group:managed:` (the staff group: members,
|
|
502
|
+
bundles and existence the platform's, its permissions editable — placed ABOVE `iam-refused:`) and
|
|
503
|
+
`iam-refused:` (a managed definition or member, the last owner, an empty organization); every tool
|
|
504
|
+
runs in `answering`. The writes map to their read for the handover (`STATUS_TOOL_OF`). No
|
|
505
|
+
staff-synchronization tool.
|
|
506
|
+
- Specs: `tests/iam-tools.spec.ts` (hosts, annotations, every refused shape with no call — revoke-only
|
|
507
|
+
modes on assign included —, the calls made, `inherit` and records revokes, held/restricted counts and
|
|
508
|
+
the `bundle` origin, the staff group's line, the staff-group and generic refusals), `remote.spec.ts`
|
|
509
|
+
(scope placement, the account-base listing).
|
|
510
|
+
|
|
511
|
+
## What the browser streams is read by cursor
|
|
512
|
+
|
|
513
|
+
The browser's sockets have no connector twin; their content does, as three read-only cursor feeds
|
|
514
|
+
(group `feeds` in `PLATFORM_CATALOGUE.capabilities`, rendered by `feedToolHelper` in `tools/feed.ts`):
|
|
515
|
+
**`project_activity { projectId?, after?, limit?, wait?, detail? }`** and **`notifications { after?,
|
|
516
|
+
wait? }`** on any host, **`file_changes { projectId?, after?, wait? }`** on a cloud target. Members
|
|
517
|
+
`ConnectorApi.project.activity`, `account.notifications`, `files.changes` over `connect.project.activity`,
|
|
518
|
+
`connect.account.notifications`, `connect.files.changes`; answers `ConnectFeedPage { cursor, entries,
|
|
519
|
+
gap }` (`ConnectFileChanges` adds `watching`).
|
|
520
|
+
|
|
521
|
+
- **The cursor is ECHOED** in every answer's structured content (`cursor`, `gap`, `entries`), and the
|
|
522
|
+
text ends with the exact call that reads on (`feedToolHelper.nextCall`: the same arguments, `after:
|
|
523
|
+
<cursor>`, `wait: 20`). Without `after` a read answers the latest entries; an empty feed answers
|
|
524
|
+
`CONNECT_FEED_START`. A `gap` line sends the parent to the domain status, never to a replay.
|
|
525
|
+
- `wait` is at most `CONNECT_FEED_WAIT_MAX_SEC` (20 s) — inside the 45 s deadline; the remote API gives
|
|
526
|
+
a held read ten seconds beyond its wait (`feedTimeout`) and sends only the query fields named.
|
|
527
|
+
- `detail: thinking` adds the model's own words (coalesced excerpts); the default `progress` leaves
|
|
528
|
+
them out. One line per entry, folded and cut at `FEED_LINE_MAX`; a toast is phrased from
|
|
529
|
+
`FEED_TOAST_WORDS`, never shown as its marker. `file_changes` says when nothing watches the tree
|
|
530
|
+
(`watching: false` — `preview_control { action: start }`).
|
|
531
|
+
- Every feed tool runs in `answering`. Specs: `tests/feed-tools.spec.ts` (hosts, annotations, the
|
|
532
|
+
echoed cursor and the read-on call, the gap and empty answers, the toast phrasing, `watching`, a
|
|
533
|
+
refusal answered, every kind one bounded line), `remote.spec.ts` (paths, queries, deadlines).
|
|
534
|
+
|
|
535
|
+
## The project settings are ONE record, and the platform is the only validator
|
|
536
|
+
|
|
537
|
+
`project_settings` and `update_project_settings` read and change what a person edits on the project's
|
|
538
|
+
control panel — the copyright line, the organization name, the Terms and Privacy links and the Google
|
|
539
|
+
tag — through `ConnectorApi.projectBranding(projectId, scope?)` and
|
|
540
|
+
`saveProjectBranding(projectId, patch, scope?)` (`connect.project.branding.get` / `.save`,
|
|
541
|
+
`ConnectProjectBranding` / `ConnectProjectBrandingSave` from `@owlmeans/viable-common`). Every
|
|
542
|
+
settings and configuration tool takes `scope` (`CONFIG_SCOPES`: `ephemeral` or `production`) —
|
|
543
|
+
production's own set, taken at its next Publish (`settingsHelper.settingsReach(target, scope)` says so).
|
|
544
|
+
|
|
545
|
+
- **A save is a PATCH.** `settingsHelper.settingsPatch` keeps exactly the settings the call named, trimmed; an
|
|
546
|
+
omitted one keeps its stored value, and an EMPTY string is sent as given — for the Google tag that
|
|
547
|
+
is the removal, for the others a value the platform refuses. A call naming none is refused locally
|
|
548
|
+
and saves nothing. The answer is the merged record the platform stored, led by where the change
|
|
549
|
+
shows (`settingsHelper.settingsReach`): a cloud preview is rebuilt, production takes it at the next Publish; a
|
|
550
|
+
local project gets it in its `.env` and `run_local` builds with it.
|
|
551
|
+
- **`useOrganizationDefaults: true`** calls `copyOrganizationBranding` instead — ALONE: with any
|
|
552
|
+
other setting the tool refuses before calling anything, so one call never asks for two pushes.
|
|
553
|
+
- **The connector states the rules and checks none of them.** `PROJECT_SETTINGS[].rule` is the web
|
|
554
|
+
save's validation in words — never-empty copyright and organization; an `https://` address without
|
|
555
|
+
credentials or a single-slash path on the application (`/terms`, `/privacy` are the generated
|
|
556
|
+
pages); a `GTM-`/`G-`/`GT-`/`AW-`/`DC-` id or `''` — used by the tool description and by the
|
|
557
|
+
refusal. A second copy of the check would be a second answer that drifts from the platform's.
|
|
558
|
+
- **A refusal names the setting and its rule.** The platform refuses a field with
|
|
559
|
+
`AuthenPayloadError(<wire field>)`; the `authen:payload:` phrase answers a known setting with its
|
|
560
|
+
label and rule and "Nothing was changed", any other field with a generic sentence. The tool runs in
|
|
561
|
+
`answering`, so the refusal is its own `isError` result and is still logged.
|
|
562
|
+
- **The save attaches the connector first** (`ensureSession`), like a story mutation: it ends in the
|
|
563
|
+
platform's configuration push, which for a local project is a `Configure` operation this connector
|
|
564
|
+
answers.
|
|
565
|
+
- **A relative link is annotated, never "fixed"**: `/terms` renders as "the generated Terms page", so
|
|
566
|
+
a parent does not rewrite it into an absolute address that points away from the generated page.
|
|
567
|
+
- **The credit is read here and switched elsewhere.** `ConnectProjectBranding.credit` renders as one
|
|
568
|
+
line (`settingsHelper.creditLine`: hidden; shown with the request kept until the plan includes
|
|
569
|
+
white label; shown and hideable; shown and not on the plan). `set_platform_credit { hidden }` →
|
|
570
|
+
`setPlatformCredit` is its own gated route: a plan without white label is a `capability-required:`
|
|
571
|
+
person refusal, phrased and notified inside the tool (`answering`). The patch never carries it.
|
|
572
|
+
|
|
573
|
+
## Configuration variables: a backend value is never shown
|
|
574
|
+
|
|
575
|
+
`project_configuration`, `update_project_configuration { backend?, frontend? }` (maps of
|
|
576
|
+
NAME → value; `configHelper.configBody` turns them into the wire's `ConnectConfigValue` lists, a side
|
|
577
|
+
the call did not name left out) and `recollect_configuration` (the platform's own model run) call
|
|
578
|
+
`ConnectorApi.config.*`; `configHelper.renderConfiguration` prints a backend variable as `set` /
|
|
579
|
+
`NOT SET` — never a value, even one an older platform sent — and a frontend variable with its public
|
|
580
|
+
value, then what still needs one. A save's answer names the variables set and never echoes a value.
|
|
581
|
+
Writes call `ensureSession` first. A refused name (`authen:payload:<NAME>` — malformed, named twice,
|
|
582
|
+
or already declared on the other side) reads as a sentence naming it.
|
|
583
|
+
|
|
584
|
+
## The organization's defaults
|
|
585
|
+
|
|
586
|
+
`organization_branding`, `update_organization_branding { organizationName?, copyright? }` (a PATCH,
|
|
587
|
+
trimmed; a call naming neither is refused locally) and `backfill_project_branding` call
|
|
588
|
+
`ConnectorApi.account.branding.*` (`settingsHelper.renderOrganizationBranding`). A project keeps its
|
|
589
|
+
own copy after creation: `update_project_settings { useOrganizationDefaults: true }` copies them in.
|
|
590
|
+
|
|
591
|
+
## The person's own records: inference, tokens, consents, the intent pickup
|
|
592
|
+
|
|
593
|
+
All `toolHostHelper.anyHost`, rendered by `accountHelper` (`tools/account.ts`); groups `inference`,
|
|
594
|
+
`access-tokens`, `privacy`, `intent` in `PLATFORM_CATALOGUE.capabilities`.
|
|
595
|
+
|
|
596
|
+
- `inference_settings { projectId? }` (read-only: the default, the attached or named project's
|
|
597
|
+
override and its effective mode, whether the plan allows `local`) and `set_inference_mode { level:
|
|
598
|
+
account | project, mode: cloud | local | inherit, projectId? }` (`inherit` = `null`, a project
|
|
599
|
+
only; an account `inherit` is refused locally). Both end with `INFERENCE_REACH`: the setting is the
|
|
600
|
+
default of the web application and connector hosts. A running stdio connector keeps its resolved
|
|
601
|
+
mode until restarted; explicit `--llm` or `VIABLE_LLM` overrides outrank the account preference.
|
|
602
|
+
A plan without the local mode is the
|
|
603
|
+
`capability-required:` person refusal, notified.
|
|
604
|
+
- `list_access_tokens` (read-only, never a secret) and `revoke_access_token { tokenId, confirm }`
|
|
605
|
+
(destructive, idempotent; revoking this connector's own token signs it out). No tool creates a
|
|
606
|
+
token. A foreign id is phrased (`authorization:forbidden:token`).
|
|
607
|
+
- `privacy_choices` (read-only, the SAVED answers: given / not given / never answered / wording
|
|
608
|
+
revised) and `withdraw_marketing_consent { keys | all }` (`all` reads the status and withdraws the
|
|
609
|
+
given ones; both together refused). No tool gives a consent. An unknown key is phrased
|
|
610
|
+
(`marketing-consent:unknown:`).
|
|
611
|
+
- `pickup_intent { code }` — the ref, or the whole `/start?ref=…` address (`accountHelper.intentRefOf`);
|
|
612
|
+
answers the prompt and points at `create_project` once the user confirms. `viable-intent:expired:`
|
|
613
|
+
and `viable-intent:throttled:` are phrased.
|
|
614
|
+
- Spec: `tests/account-tools.spec.ts`.
|
|
615
|
+
|
|
616
|
+
## Planning kits are described, then applied; the platform writes and rebuilds
|
|
617
|
+
|
|
618
|
+
A planning kit is a ready set of card types and status flows for one kind of work-management
|
|
619
|
+
product, which the platform writes into the target's common package (`PLANNING`).
|
|
620
|
+
`describe_planning_kits` (`ConnectorApi.project.kitDescribe(projectId)`, GET
|
|
621
|
+
`connect.project.kit.describe` → `{ kits: PlanningKitView[] }`) lists each kit's purpose, container,
|
|
622
|
+
types with their main flow and each flow's statuses; it opens no session. `apply_planning_kit`
|
|
623
|
+
(`project.kitApply(projectId, { kit, types? })`, POST `connect.project.kit.apply` →
|
|
624
|
+
`{ applied, skipped, warnings }`) refuses a call without `kit` locally, attaches the connector first
|
|
625
|
+
(`ensureSession`: the write is a file operation a local project's connector answers), runs in
|
|
626
|
+
`answering`, and answers what was written, left out and warned. `types` keeps those kit type keys;
|
|
627
|
+
omitted keeps all. Both are offered on every host, in the `planning-kits` capability group. The SDK
|
|
628
|
+
never renders the literals or rebuilds — the platform does, inside the 45-second ceiling.
|
|
629
|
+
|
|
630
|
+
## `describe_platform` also says what a generated application CARRIES
|
|
631
|
+
|
|
632
|
+
`PLATFORM_CATALOGUE.features` holds facts about the product the runs produce — the landing gate, the
|
|
633
|
+
generated Terms and Privacy pages, the consent-gated Google tag, the look, production builds without
|
|
634
|
+
preview scaffolding — rendered whole on every host under "WHAT A GENERATED APPLICATION CARRIES", with
|
|
635
|
+
only their `tools` narrowed to what the host offers. A parent that is not told the platform generates
|
|
636
|
+
the legal pages writes its own beside them. `describe_capabilities` ends with `GENERATED_SUMMARY`, the
|
|
637
|
+
same facts in one sentence, because it is read at the same moment by a parent that may never call
|
|
638
|
+
`describe_platform`; the two sit side by side in `platform.ts`. The pipelines' `stages` name the steps
|
|
639
|
+
a run can stop at (`landing` and `legal` in init, `landing` in story development).
|
|
640
|
+
|
|
641
|
+
## A balance, consent, confirmation or plan refusal is phrased for a person, and pushed through `notify`
|
|
642
|
+
|
|
643
|
+
`refusalHelper.personRefusalPhrase(e, retry?)` (`tools/refusal.ts`) phrases the refusals only a PERSON can
|
|
644
|
+
resolve — the three of `@owlmeans/viable-common` `connect/errors.ts` by `instanceof`, and the plan's
|
|
645
|
+
two by MARKER, because `CapabilityRequired` / `LimitExhausted` are declared in `@owlmeans/payment`,
|
|
646
|
+
which this package does not depend on — and answers `null` for anything else:
|
|
647
|
+
`capability-required:<param|param>` → `capabilityRequiredPhrase` (the feature named from
|
|
648
|
+
`CAPABILITY_LABELS`, the plan changed in Billing, nothing the parent does unlocks it) and
|
|
649
|
+
`limit-exhausted:<key>:<used>/<limit>[:<ISO>]` → `limitExhaustedPhrase` (the limit, how full, the
|
|
650
|
+
renewal day). Both also stand in `REFUSALS` for the stored-text path. Both `registerCatalogue`'s catch and the conversion tools' `answering` use it, so a
|
|
651
|
+
conversion's balance refusal reads exactly like any other tool's:
|
|
652
|
+
|
|
653
|
+
- `ConnectOutOfCredits` — rather than the raw `viable-connect:out-of-credits:...` marker, the tool
|
|
654
|
+
result reads as a sentence: what it needed, what the account has, and a link to top up.
|
|
655
|
+
- `ConnectConsentRequired` — the EU spend consent: "Nothing was started", why (credits bought less
|
|
656
|
+
than 14 days ago may only be used once a person expressly asks, the purchase still withdrawable up
|
|
657
|
+
to the last day), the `consentUrl` to open and confirm in the browser, and that the call must NOT
|
|
658
|
+
be retried automatically — only after the user says they confirmed (`refusalHelper.consentRequiredPhrase(url,
|
|
659
|
+
deadline)`, shared with the stored-text entry below).
|
|
660
|
+
- `ConnectConfirmationRequired` — a conversion step that would use the plan's conversion or spend
|
|
661
|
+
credits (`refusalHelper.confirmationRequiredPhrase(fields, retry?)`): "Nothing was started"; for a start, what the
|
|
662
|
+
plan's conversion covers (free up to `cap` credits, the conversion limit; beyond it credit limits
|
|
663
|
+
first, then topped-up credits; every stage quoted and asked first); for a stage, its estimate
|
|
664
|
+
split ("580,000 from the conversion limit, 150,000 from the organization's credit limits, $2.40
|
|
665
|
+
of topped-up credits" — credit limits as credits, topped-up credits as money) and what the limit
|
|
666
|
+
has used and left. Then: tell the user exactly this, and only after they agree make the call it
|
|
667
|
+
prints — never `confirm: true` on the model's own. Every variant names the conversion limit (a
|
|
668
|
+
conversion with no plan unit "has no conversion limit").
|
|
669
|
+
|
|
670
|
+
Their fields travel packed into the message (only `type` and `message` survive the platform's
|
|
671
|
+
internal HTTP hop) and are read back with `finalizeUnmarshal()`. All three are also handed to the
|
|
672
|
+
optional `ToolDeps.notify?('warning', text)`, which a host wires to its own out-of-band channel —
|
|
673
|
+
the stdio `viable-mcp` host sends an MCP `notifications/message`; the platform's stateless `/mcp`
|
|
674
|
+
host has no channel and omits it, so `notify` is always best-effort and optional. Every other
|
|
675
|
+
error returns through `refusalHelper.refusalPhrase` and never calls `notify`.
|
|
676
|
+
|
|
677
|
+
**A conversion's confirmation is answered inside its tool, with the exact call to repeat**
|
|
678
|
+
(`confirming` in `catalogue.ts`): `convert_project` / `proceed_conversion` print
|
|
679
|
+
`<tool> {"projectId":…[,"decision":…,"note":…],"confirm":true}` — the project NAMED, because the
|
|
680
|
+
URL-configured host keeps no attachment between calls and a bare repeat of `convert_project` there
|
|
681
|
+
would file a second conversion (the start is what a confirmation stops; the create before it is
|
|
682
|
+
never repeated). Both take `confirm` (`z.boolean().default(false)`, described), sent only when the
|
|
683
|
+
parent passed `true`, and their descriptions explain the conversion limit (1,000,000 credits today),
|
|
684
|
+
the spend order and that a delegated conversion is never asked — so a parent can say it before it is
|
|
685
|
+
refused. A production body carries only the status: a bare `api:client:status:428` to a call sent
|
|
686
|
+
WITHOUT `confirm` is answered with `refusalHelper.unconfirmedConversionPhrase(retry)` (the confirmation, and the
|
|
687
|
+
consent as what a second refusal of the confirmed call would mean); to a confirmed call it can only
|
|
688
|
+
be the consent and falls to `REFUSALS`. `serverInstructions` says the same in one sentence of the
|
|
689
|
+
workflow paragraph.
|
|
690
|
+
|
|
691
|
+
`proceed_conversion` also takes the person's edits flat — `name`, `description`, `specification`,
|
|
692
|
+
`vision`, `designSystem` — and sends the ones given nested as `update` (`PROCEED_UPDATE_FIELDS`);
|
|
693
|
+
the platform accepts them only with the decision that starts the extraction, and the printed repeat
|
|
694
|
+
call carries them too.
|
|
695
|
+
|
|
696
|
+
The integrations' refusals each have a sentence in `REFUSALS` as well: `github:not-connected`,
|
|
697
|
+
`github:not-published`, `oauth:invalid-or-expired-state` (the GitHub authorization),
|
|
698
|
+
`viable-domain:taken:` (a custom domain held by another project), `iam:group:managed:` (the staff
|
|
699
|
+
group's members, bundles or existence) and `iam-refused:` (the app's own sign-in refusing a console
|
|
700
|
+
change on its terms).
|
|
701
|
+
|
|
702
|
+
The same refusals also arrive where no class survives, and `REFUSALS` phrases them by marker:
|
|
703
|
+
`viable-connect:consent-required:` (a stored run error; the URL and deadline parsed from the
|
|
704
|
+
detail), `viable-connect:confirmation-required:` (`ConnectConfirmationRequired.decode` of the
|
|
705
|
+
detail, the generic "same arguments and confirm: true" repeat), `performance-consent-required` (the web refusal inside a planning `commit-failed:` or a
|
|
706
|
+
stored error — no URL, so "Billing in the OwlMeans web application") — both ABOVE the planning
|
|
707
|
+
markers, because the consent is what the person acts on — and, for a production body that carries
|
|
708
|
+
only an incident id (`@owlmeans/api` `ApiStatusError`), `api:client:status:428` (the consent
|
|
709
|
+
sentence) and `api:client:status:402` (the balance sentence).
|
|
710
|
+
|
|
711
|
+
## The session loop: a pull loop that never stops, a serial worker, and free to redeliver
|
|
712
|
+
|
|
713
|
+
`openSession` runs two loops. The PULL loop files model tasks and questions for the parent and hands
|
|
714
|
+
every local operation (`SlotCommand`, `Configure`) to the WORKER, which answers them **one at a time**.
|
|
715
|
+
Serial because the thing on the other end of a local operation is a filesystem the platform believes
|
|
716
|
+
it is the only writer of — two concurrent template writes into one tree is not a throughput problem,
|
|
717
|
+
it is a corrupted tree. Separate because a local command can take minutes (`bun install`), and a
|
|
718
|
+
model task the platform is blocked on must still be delivered meanwhile. An operation queued or
|
|
719
|
+
running on the worker is not queued again when it is redelivered. A pull that came back empty in
|
|
720
|
+
under `FAST_PULL_MS` (1 s) is followed by `EMPTY_PULL_PAUSE_MS` (1.5 s) of rest.
|
|
721
|
+
|
|
722
|
+
The task queue hands out the task with the earliest `expiresAt` first (a synchronous check expires
|
|
723
|
+
in seconds, a run's call in minutes). `take(waitMs, signal?)` is cancellable and REMOVES an aborted
|
|
724
|
+
waiter — a leftover one swallows the next item, held as outstanding and shown to nobody — and
|
|
725
|
+
`available(waitMs, signal?)` waits for a queued item without consuming it.
|
|
726
|
+
|
|
727
|
+
Results are remembered by operation id. A connector that reconnects is handed everything still
|
|
728
|
+
outstanding, **including whatever it had already answered when the connection dropped**, and the
|
|
729
|
+
cached result answers it without doing the work again — re-executing a build or a model task because
|
|
730
|
+
an acknowledgement was lost is exactly the cost this avoids. A failed `submitOp` is logged, never
|
|
731
|
+
thrown: the operation stays in the platform's store and is redelivered.
|
|
732
|
+
|
|
733
|
+
Model tasks are **not executed here at all** — they are the parent agent's to run in its own clean
|
|
734
|
+
subagent, and what the loop owes them is delivery. The operation id is kept beside each task and
|
|
735
|
+
never shown to the parent: a parent that could name one could answer an operation it was never
|
|
736
|
+
given.
|
|
737
|
+
|
|
738
|
+
A local operation that fails reports `ConnectOpErrorKind.Refused`, never `Unavailable`. The
|
|
739
|
+
connector ran and could not do the thing; `Unavailable` tells the platform to give up on a run that
|
|
740
|
+
is fine.
|
|
741
|
+
|
|
742
|
+
**The pull long-poll is the only transport.** It works through every proxy and needs no
|
|
743
|
+
reconnection logic, and the URL-configured host opens no session at all, so it never pulls. The
|
|
744
|
+
contract declares no connector socket, and `SessionStats.transport` is `'pull'` or `'none'`;
|
|
745
|
+
presence is refreshed by every pull and every submit.
|
|
746
|
+
|
|
747
|
+
## An answer is READ in the shapes models produce, not only the one asked for
|
|
748
|
+
|
|
749
|
+
The tool-call instruction asks for a JSON array and models mostly comply — but a single call comes
|
|
750
|
+
back as a bare object often enough, and some wrap the array in `{tool_calls: […]}`, that assuming
|
|
751
|
+
the array is a defect rather than strictness. The reference driver cast straight to an array, so a
|
|
752
|
+
bare object threw `parsed.map is not a function`; the TypeError was handed back AS the answer, the
|
|
753
|
+
model ladder read that as a bad answer and asked again with feedback that said nothing about what
|
|
754
|
+
was wrong, and the story failed with `retry-exceeded` having never been told. `toolCallsOf` accepts
|
|
755
|
+
all three shapes and still throws for an answer carrying no call at all — an empty answer is a bad
|
|
756
|
+
answer, and the retry is the right response to it.
|
|
757
|
+
|
|
758
|
+
The same rule is why `makeTaskEnvelopeModel(task).parseTaskResult` refuses with a described reason rather than a stack trace:
|
|
759
|
+
whatever comes back becomes a model's next prompt, so it has to be readable by one.
|
|
760
|
+
|
|
761
|
+
## A redelivered model task must NOT be handed over twice
|
|
762
|
+
|
|
763
|
+
The platform redelivers every unanswered operation on each poll — that is what makes a connector
|
|
764
|
+
restart cost a round trip instead of a run, and for a slot command it is free because the result
|
|
765
|
+
cache answers it. A MODEL task is different: it is answered by a parent agent in tens of seconds,
|
|
766
|
+
and every poll during that window used to queue another copy. The parent then ran the same task
|
|
767
|
+
over and over — real model calls, paid for by the user, thrown away — while the job stayed blocked,
|
|
768
|
+
because only the first answer routed to a live operation and every later one reported that no
|
|
769
|
+
operation was waiting.
|
|
770
|
+
|
|
771
|
+
`TaskQueue` therefore remembers every task id it has handed over and ignores a second delivery of
|
|
772
|
+
one, keeping the memory after the task settles so a redelivery that raced the platform's deletion
|
|
773
|
+
is ignored rather than answered again. The operation id IS refreshed on a redelivery, because the
|
|
774
|
+
answer must route to the operation the platform is currently waiting on. `push` returns whether the
|
|
775
|
+
task was accepted, and `tasksDelivered` counts only accepted ones — a counter that grew per poll
|
|
776
|
+
would make a stuck run look busy.
|
|
777
|
+
|
|
778
|
+
## The task envelope is text for a model to act on
|
|
779
|
+
|
|
780
|
+
The reader is a language model working from a tool result, so it must be able to act without a
|
|
781
|
+
schema in front of it. Everything it must do is stated **in order and before the material** — how to
|
|
782
|
+
run it (in a clean subagent, per harness), what to call afterwards (`submit_task_result`, with the
|
|
783
|
+
id spelled out), and what shape the answer takes — so a model that stops reading early has still
|
|
784
|
+
read the instruction. The task id appears at the top and in the follow-up call because it is the
|
|
785
|
+
only thing that routes an answer back: a parent that paraphrases the rest but copies the id still
|
|
786
|
+
works. A retry says which attempt it is and why the previous answer was refused. Harnesses differ
|
|
787
|
+
only in the isolation mechanism, never in what is asked; `Other` gets the request stated rather than
|
|
788
|
+
mechanised. Record-interaction tasks preserve their complete system instructions and JSON answer
|
|
789
|
+
schema, including host/parent bindings and `selectionComponents`, through every harness. They need
|
|
790
|
+
no navigation-specific MCP arguments: guided story tools and direct free flight share the platform's
|
|
791
|
+
record-placement and wiring capabilities.
|
|
792
|
+
|
|
793
|
+
For Codex, the parent gives a fresh `viable-worker` only the task's system prompt, then its
|
|
794
|
+
conversation, then the requested result shape: the outer handoff instruction and
|
|
795
|
+
`submit_task_result` call are parent-only. It passes that final response through unchanged, but must
|
|
796
|
+
preserve the mode: a `text` task returns the requested source or text and never a JSON tool-call
|
|
797
|
+
array; that array belongs only to `tools` mode.
|
|
798
|
+
|
|
799
|
+
**`makeTaskEnvelopeModel(task).parseTaskResult` refuses a malformed answer locally**, and returns a `problem` rather than
|
|
800
|
+
throwing. The refusal is worth more than the parse: an answer that reaches the platform malformed
|
|
801
|
+
costs a whole retry — another task, another subagent, another wait — while one caught here is a
|
|
802
|
+
sentence the parent can act on immediately, with the subagent's context still open. It unfences,
|
|
803
|
+
rejects an array or a scalar in `Json` mode, and rejects a tool name the task never offered.
|
|
804
|
+
|
|
805
|
+
A task is handed out ONCE, and the platform then waits on it for up to 45 minutes. So the loop has
|
|
806
|
+
to survive a parent that lost the envelope — a compacted conversation, a subagent that died before
|
|
807
|
+
answering. `next_task` takes an optional `taskId` that re-reads a task already handed out, and when
|
|
808
|
+
nothing new is queued but something is unanswered it NAMES the outstanding ids rather than re-handing
|
|
809
|
+
them: re-handing would have a parent whose subagent is still working run the same task twice. A
|
|
810
|
+
refused answer keeps the task outstanding for exactly the same reason.
|
|
811
|
+
|
|
812
|
+
## Extension tools come from the host, never from this package
|
|
813
|
+
|
|
814
|
+
The catalogue is closed over what the SDK itself implements. A hosting process that offers more —
|
|
815
|
+
the stdio connector adds `describe_harness` and `install_harness`, which write a coding agent's
|
|
816
|
+
instruction, subagent and MCP files — hands them in as `ToolHost.extensions`. `catalogueHelper
|
|
817
|
+
.visibleTools(host)` appends them to the catalogue and filters both by `availability`, so
|
|
818
|
+
`registerCatalogue`, `renderPlatform` and `serverInstructions` treat an extension exactly like a
|
|
819
|
+
built-in tool; a host that passes none offers none, and the platform catalogue's `harness` group
|
|
820
|
+
renders as absent there. A group may name a tool only the catalogue or `EXTENSION_TOOLS` knows —
|
|
821
|
+
`platform.spec.ts` holds that line. This package never imports a host's extension: the SDK is
|
|
822
|
+
public, the agent-setup pair is not.
|
|
823
|
+
|
|
824
|
+
## The executor is the publisher's job, on somebody's laptop
|
|
825
|
+
|
|
826
|
+
`makeLocalSlotExecutor(dir)` answers the platform's slot commands against a directory here. The
|
|
827
|
+
dispatcher is the publisher's, switch for switch: the platform's remote helpers parse the ANSWERS
|
|
828
|
+
and cannot tell whether a pod or a laptop produced one, so an ordinary failure comes back in the
|
|
829
|
+
same shape ("error text or null"), and only an unknown command — a protocol fault rather than a
|
|
830
|
+
project fault — escapes as an exception. Everything is resolved per call rather than closed over,
|
|
831
|
+
because a re-initialization replaces the tree under a running connector. The integrity verdict is
|
|
832
|
+
forgotten after every command that changes the tree.
|
|
833
|
+
|
|
834
|
+
Rules for the local executor:
|
|
835
|
+
|
|
836
|
+
- **The local browser environment defaults `APP_UNSECURE` to `true`** so its generated client
|
|
837
|
+
calls the HTTP web proxy. The web `.env` can override this default; root backend secrets never
|
|
838
|
+
enter the browser environment. The platform's local configure uses the web port for both
|
|
839
|
+
`FRONTEND_PORT` and `BACKEND_PORT`, with `/api` proxied to the backend port.
|
|
840
|
+
|
|
841
|
+
- **`Bun` forwards `options.env` into the child process**, alongside `options.subproject` for
|
|
842
|
+
its working directory. The spawn helper merges these values over the inherited environment
|
|
843
|
+
without changing the parent process or the project's configuration files. Runtime probes carry
|
|
844
|
+
their input in these variables; dropping them prevents the runner from producing its report.
|
|
845
|
+
`executor-shell.spec.ts` exercises the public slot-command dispatcher with a real Bun child.
|
|
846
|
+
|
|
847
|
+
- **`makeTargetEnvHelper(dir).backendEnv` reads the root `.env` and `.frontendEnv` the web package's own — the file split IS
|
|
848
|
+
the leak boundary.** The publisher has an allow-list (`frontendEnvVars`/`frontendSecrets`); here
|
|
849
|
+
the two files are never merged in either direction, because `FRONTEND_ENV_KEYS` announces
|
|
850
|
+
everything in the frontend set to the bundler and a merge would compile `OIDC_SECRET` and
|
|
851
|
+
`DATABASE_URL` into a bundle every visitor downloads.
|
|
852
|
+
- **`Discard` passes `git clean -fd -e .viable`.** `.viable/connect.json` is untracked until someone
|
|
853
|
+
commits it, and a discard that swept it leaves a tree no later session can recognise as a platform
|
|
854
|
+
project at all.
|
|
855
|
+
- **The always-keep list must not leave empty parent shells.** A wipe keeps `.viable`, `.env`, the
|
|
856
|
+
web package's `.env` and `.git` whatever the caller asked, so a directory holding a kept path is
|
|
857
|
+
walked and pruned rather than removed — and a keep that was not there (the web `.env` every
|
|
858
|
+
project without local branding lacks) must not leave the walked directory behind as a skeleton the
|
|
859
|
+
install then lands in.
|
|
860
|
+
- **An absolute path from elsewhere on the machine is re-rooted inside the project**, not refused —
|
|
861
|
+
the publisher's own behaviour, kept deliberately and pinned by a test. Concatenating it onto the
|
|
862
|
+
root instead builds a shadow tree: the write reports success, a later read of the same name
|
|
863
|
+
resolves to the untouched original, and the change looks silently ignored. `resolveInProject`
|
|
864
|
+
(exported as `confineToProject`) then compares the fully resolved path to the root — confinement,
|
|
865
|
+
not sanitization, and applied on **every** fs call rather than a `..`-to-`.` replace at one caller.
|
|
866
|
+
- **`run/state.ts` sits below both the shell commands and the run**, so the three do not form a
|
|
867
|
+
cycle: `DbSync` bounces the api child, `DbSync` is a shell command, and the shell commands are
|
|
868
|
+
reached through the executor the run is built on. Everything that touches a child process lives
|
|
869
|
+
there, and the run record is a file because the processes outlive the call — and, on a restarted
|
|
870
|
+
connector, the process — that started them.
|
|
871
|
+
|
|
872
|
+
## Attaching a session is what writes the project marker
|
|
873
|
+
|
|
874
|
+
`.viable/connect.json` is how a connector started in a directory tomorrow knows which project it
|
|
875
|
+
is, and `attach_project` finds a project by its directory through it. It was declared, read at MCP
|
|
876
|
+
startup and documented in `mcp.md` before anything wrote it — a marker nothing writes is a lookup
|
|
877
|
+
that always misses.
|
|
878
|
+
|
|
879
|
+
`openSession` writes it when the session names a project and a directory, which is the moment the
|
|
880
|
+
directory becomes that project's. Best-effort and idempotent: a session that works is worth more
|
|
881
|
+
than a note about it, and it is skipped when the marker already names the same project. It holds
|
|
882
|
+
no secret and is meant to be committed, which is why the API URL and the slug are in it and the
|
|
883
|
+
token never is.
|
|
884
|
+
|
|
885
|
+
## The managed block yields to every key the user assigned
|
|
886
|
+
|
|
887
|
+
A `.env` gives the LAST assignment of a key and the platform's block is appended, so a platform line
|
|
888
|
+
silently outranks whatever the user wrote above it. That made the documented promise — everything
|
|
889
|
+
outside the block is yours — false for the one value the platform cannot supply: `DATABASE_URL`, the
|
|
890
|
+
line the setup guide tells people to write. The application connected to the placeholder no matter
|
|
891
|
+
what its owner put in the file.
|
|
892
|
+
|
|
893
|
+
`makeProjectEnvHelper(dir).writeEnv` therefore reads the file with the block cut out, and replaces any block line assigning a
|
|
894
|
+
key the user already assigns with a comment saying so. The rule is about VALUES, not lines: a key the
|
|
895
|
+
user has not claimed is still the platform's to set, and a key they later delete goes back to the
|
|
896
|
+
platform on the next push.
|
|
897
|
+
|
|
898
|
+
## The two services a local project needs are ASKED for, never assumed
|
|
899
|
+
|
|
900
|
+
The platform provisions nothing on somebody's own machine, so a database — and, for a project with a
|
|
901
|
+
worker, a queue store — is the one thing it cannot supply and must not guess. `local_setup_guide`
|
|
902
|
+
reports what the machine already provides and, where something is missing, returns a QUESTION with
|
|
903
|
+
three answers: the user already has one, wants one installed here, or wants a free hosted plan.
|
|
904
|
+
Guessing means either a container on somebody's machine they did not ask for, or a connection string
|
|
905
|
+
invented for a database that does not exist — and the second surfaces minutes later as a boot error
|
|
906
|
+
about a connection, which sends the reader looking for a database rather than for a typo.
|
|
907
|
+
|
|
908
|
+
`set_local_service` records what they answered: it writes the value into the project's own half of
|
|
909
|
+
the `.env`, replacing that key rather than appending a second copy, and PROBES it immediately, so a
|
|
910
|
+
string that does not connect is caught while the user is still in the conversation. A credential
|
|
911
|
+
never reaches the marker, a harness configuration, or the platform, and the guide warns when the
|
|
912
|
+
file is not git-ignored.
|
|
913
|
+
|
|
914
|
+
## A detached child outlives the connector, so the port is RECLAIMED before it is used
|
|
915
|
+
|
|
916
|
+
Every target process is spawned detached — that is what keeps a build from dying with the tool that
|
|
917
|
+
started it — and detaching is also why a connector killed mid-run (a host restart, a Ctrl-C, a
|
|
918
|
+
crashed test) leaves one alive. What survives holds port 3000, and the next attempt fails with the
|
|
919
|
+
TARGET's own "the api port is already in use", which reads as a defect in the generated app rather
|
|
920
|
+
than as a leftover.
|
|
921
|
+
|
|
922
|
+
Two rules, both ported from the publisher because a laptop needs them more than a pod does:
|
|
923
|
+
- `spawnHelper.killGroupAndWait` never returns early on the child's own exit code. The child is a shell wrapping
|
|
924
|
+
`bun`, so a signal the shell dies from leaves `bun` alive while `exitCode` says gone; the wait
|
|
925
|
+
polls the GROUP instead of the child's exit event.
|
|
926
|
+
- `spawnHelper.reclaimPort` runs before a spawn as well as after one, and matches on the script AND the process
|
|
927
|
+
marker (`--viable-api`, `--viable-worker`, `--viable-boot-check`) — never a port alone, and never
|
|
928
|
+
a marker alone, since reclaiming sends SIGKILL and a marker on its own matches any command line
|
|
929
|
+
that merely mentions it.
|
|
930
|
+
|
|
931
|
+
## Running the generated app locally
|
|
932
|
+
|
|
933
|
+
`makeLocalRunHelper(dir).runLocal` builds and starts the api, the worker (when the project has one) and a local server that
|
|
934
|
+
holds the web port and proxies `/api` to the api process on the same origin — the generated app's
|
|
935
|
+
configuration assumes one origin, and splitting them across two loopback ports would put its CORS
|
|
936
|
+
setup in the path of a developer's first page load. There is no respawn ladder, no reconciler and no
|
|
937
|
+
health route: a developer watching their own terminal is the supervisor. What IS reproduced is the
|
|
938
|
+
shape — the same ports, the same argv marker, the same environment, and the same "port ownership is
|
|
939
|
+
the only proof" rule about restarts. Nothing from the publisher's public-hostname policy (robots,
|
|
940
|
+
CSP, the preview error reporter, the build-state header) is reproduced: a loopback address has no
|
|
941
|
+
reputation to spend and no crawler to answer.
|
|
942
|
+
|
|
943
|
+
`run_local` refuses to start a project whose `.env` is still missing a required key, because an app
|
|
944
|
+
launched without a database URL fails at boot with a message about a connection, which sends the
|
|
945
|
+
reader looking for a database that was never configured.
|
|
946
|
+
|
|
947
|
+
## Tests
|
|
948
|
+
|
|
949
|
+
In the `@owlmeans/viable-sdk` package directory, `bun x tsc -p tests/tsconfig.json --noEmit` checks the SDK sources and all test typings.
|
|
950
|
+
|
|
951
|
+
`bun test ./tests` — offline: the envelope and its parser, the tool catalogue and its extension seam,
|
|
952
|
+
the `registerCatalogue` out-of-credits, consent and planning-refusal phrasing and `notify` wiring
|
|
953
|
+
(`mcp-catalogue.spec.ts`; the consent marker, the bare 428/402 statuses and a consent inside a
|
|
954
|
+
commit failure in `catalogue.spec.ts`; the conversion confirmation — schemas and descriptions, the
|
|
955
|
+
start and stage phrasing, the repeat call naming a freshly filed project, the stored marker, the
|
|
956
|
+
bare 428 either side of `confirm`, the confirmed call's balance and consent refusals and the MCP
|
|
957
|
+
boundary — in `conversion-confirmation.spec.ts`; the batch, stop and steer tools — attach order, codes
|
|
958
|
+
resolved to card ids, the batch rendering and each final status's `next:`, cancel-then-stop, no call
|
|
959
|
+
without `confirm`, only the named stop fields, the stopping/steerable status lines and the three
|
|
960
|
+
refusals at the MCP boundary — in `run-control.spec.ts`), the executor's files/git/layout rules, and the marker + managed-`.env`
|
|
961
|
+
block. The story tools run over a REAL `@owlmeans/server-planning` service (memory store, the Viable
|
|
962
|
+
types and flows, one plugin standing in for the platform's format seam) built in `tests/context.ts`,
|
|
963
|
+
the landing mark included; the settings, configuration, credit, organization (`configuration.spec.ts`:
|
|
964
|
+
no backend value printed, the maps sent as lists, the white-label refusal phrased and notified, the
|
|
965
|
+
defaults copy alone, the remote routes and scopes) and planning-kit tools over a recorded `ConnectorApi` (order of
|
|
966
|
+
session and save or apply, the patch or kit body sent, the phrased refusal). `platform.spec.ts` pins that every tool a
|
|
967
|
+
pipeline, feature or group names exists, every tool is in exactly one group, every group has its
|
|
968
|
+
`absent` sentence, and `serverInstructions` names a tool of every group a host offers and none it hides; `planning-wiring.spec.ts` pins
|
|
969
|
+
the planning aliases and paths a context binds, and `remote.spec.ts` drives the remote facade, the
|
|
970
|
+
settings and the kit routes through a captured transport to pin their paths and deadlines, and pins that the remote
|
|
971
|
+
`ConnectorApi` has no member without a connector route (the `files`, `sandbox` and `slot` members by
|
|
972
|
+
name) and every connector alias is bound, and that a
|
|
973
|
+
delegated create keeps the tool deadline and sends its `sessionId`. `call-collect.spec.ts` drives the
|
|
974
|
+
transport through the same captured client (`captureTransport` replaces the context's API CLIENT, so
|
|
975
|
+
the SDK's transport stays in front): the header only in the delegated mode and only on writes, a
|
|
976
|
+
reused id, pending → collect → settled for a connector call and a planning execute, a settled error
|
|
977
|
+
rethrown as its class, `lost` → `ConnectCallLost`, a dropped hop retried, ordinary answers untouched,
|
|
978
|
+
and the handover over a call that went pending. `catalogue.spec.ts` also pins the manual story moves
|
|
979
|
+
over the real planning service (order, the refusal, a late commit), `delete_project` / `unlock_project_agent`
|
|
980
|
+
(no call without `confirm`, the required `projectId`, release → detach → destroy only for the attached
|
|
981
|
+
project), the annotations (declared everywhere, the destructive set, passed through `registerCatalogue`)
|
|
982
|
+
and the plan and integration phrasings, the cloud-only file and preview tools on both hosts with
|
|
983
|
+
`list_slots` everywhere, `list_files`'s kind switch (the category only with `meta`), `write_file`
|
|
984
|
+
attaching before its save and naming a failed rebuild, `delete_file` making no call without
|
|
985
|
+
`confirm`, each `preview_control` action's member, and the protected-file and missing-workload
|
|
986
|
+
phrasings; `conversion-confirmation.spec.ts` the proceed edits nested as
|
|
987
|
+
`update` and repeated in the confirmed call; `remote.spec.ts` the delete and unlock routes. `handover.spec.ts` drives the delegated
|
|
988
|
+
`registerCatalogue` over a real task queue: a task first is the answer and the submit replies with
|
|
989
|
+
the call's result, a call first answers as before, an identical repeat joins the running call, a
|
|
990
|
+
refused call is reported in `settled[]`, `next_task` names a call still running, and a delegated
|
|
991
|
+
create detaches, opens an unattached session, sends its id and attaches the new project (none of it
|
|
992
|
+
in the cloud mode). `task-queue.spec.ts` pins the cancellable take, the non-consuming availability
|
|
993
|
+
and the expiry order; `session-worker.spec.ts` that a model task is delivered while a slow slot
|
|
994
|
+
command runs and a redelivered command runs once.
|
|
995
|
+
|
|
996
|
+
## Depends On
|
|
997
|
+
|
|
998
|
+
- `@owlmeans/viable-common` — the whole wire contract · `@owlmeans/auth-token` — the carrier guard
|
|
999
|
+
- `@owlmeans/planning` — the protocol tree, the facade contract and its refusals ·
|
|
1000
|
+
`@owlmeans/client-planning` — the remote facade and the long-poll commit wait
|
|
1001
|
+
- `@owlmeans/api`, `@owlmeans/client-context`, `@owlmeans/client-entrypoint`, `@owlmeans/client-config`,
|
|
1002
|
+
`@owlmeans/auth-common`, `@owlmeans/entrypoint`, `@owlmeans/route`, `@owlmeans/socket`,
|
|
1003
|
+
`@owlmeans/config`, `@owlmeans/context`, `@owlmeans/error`, `@owlmeans/basic-ids`
|
|
1004
|
+
- `@modelcontextprotocol/sdk` (types only, structurally), `zod`, `fs-extra`, `globby`, `ajv`
|
|
1005
|
+
|
|
1006
|
+
## Blueprint discovery before project creation
|
|
1007
|
+
|
|
1008
|
+
`ConnectorApi.account.blueprints()` reads the installed `BlueprintCatalogue` from authenticated
|
|
1009
|
+
`/connect/account/blueprints`, before a project or session exists. `describe_blueprints` renders and
|
|
1010
|
+
returns its full structured catalogue: blueprint ids, stacks, resolved cases, supported application
|
|
1011
|
+
categories, tenancy and planning resource capabilities. Both stdio MCP and platform `/mcp` expose
|
|
1012
|
+
this same SDK tool. The platform bridges to the signed agent registry; never synthesize the deployed
|
|
1013
|
+
catalogue from frontend presets or a closed-package import.
|
|
1014
|
+
|
|
1015
|
+
The remote planning facade hydrates HTTP and collected receipts with empty advisory organization
|
|
1016
|
+
metadata (`entityId: ''`). Native organization IDs are absent from planning wire replies. Use the
|
|
1017
|
+
authenticated organization entity slug and the planning scope lifecycle for client isolation;
|
|
1018
|
+
never resolve permissions or organization identity from a returned planning record.
|
|
1019
|
+
|
|
1020
|
+
Server instructions explicitly describe the stock `owlmeans-fullstack-ts` cases: `web`, `scalable`,
|
|
1021
|
+
`ai-pipeline`, `ai-agent`, `game`, `work-management`, `work-management-tenanted`. Work categories
|
|
1022
|
+
include project tracking, CRM, service desk, inventory, recruiting, field service and process;
|
|
1023
|
+
game categories include casual, online-turn and online-live. The live catalogue is authoritative
|
|
1024
|
+
for installed custom registries. Read it before proposing a project category.
|
|
1025
|
+
|
|
1026
|
+
|
|
1027
|
+
## Related
|
|
1028
|
+
|
|
1029
|
+
- `@owlmeans/viable-mcp` — the npx stdio server built on this (closed-source; its skill ships in its own package)
|
|
1030
|
+
- [[auth-token]] — the credential and its carrier guard
|
|
1031
|
+
- [[client-planning]] · [[planning]] — the facade the story tools write through
|
|
1032
|
+
- `@owlmeans/llm-delegate` (`internal` monorepo, skill `llm-delegate`) — the other end of a model
|
|
1033
|
+
task, inside the platform
|