@nanobpm/nano-workforce 0.172.0 → 0.173.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/docs/mcp-runbook.md +22 -9
- package/install.sh +45 -5
- package/package.json +1 -1
- package/pages/_nav.json +1 -0
- package/pages/board.page.json +4 -0
- package/pages/cockpit.page.json +4 -0
- package/pages/delivery-graph-detail.page.json +4 -0
- package/pages/delivery-graphs.page.json +4 -0
- package/pages/epic-detail.page.json +4 -0
- package/pages/epic.page.json +4 -0
- package/pages/feature.page.json +4 -0
- package/pages/home.page.json +4 -0
- package/pages/lineage.page.json +4 -0
- package/pages/mcp.page.json +217 -0
- package/pages/overview.page.json +4 -0
- package/pages/tasks.page.json +40 -9
- package/pages/velocity.page.json +4 -0
- package/test/install-smoke.sh +7 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [0.173.0](https://github.com/nanobpm/nano-workforce/compare/v0.172.1...v0.173.0) (2026-09-01)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* **mcp:** add a deployed "Connect over MCP" console page ([#701](https://github.com/nanobpm/nano-workforce/issues/701)) ([dd2b998](https://github.com/nanobpm/nano-workforce/commit/dd2b99861ec4e2c8d46fc7e69397ac71954cf27c)), closes [#698](https://github.com/nanobpm/nano-workforce/issues/698) [#698](https://github.com/nanobpm/nano-workforce/issues/698) [#699](https://github.com/nanobpm/nano-workforce/issues/699)
|
|
6
|
+
|
|
7
|
+
## [0.172.1](https://github.com/nanobpm/nano-workforce/compare/v0.172.0...v0.172.1) (2026-09-01)
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* **onboarding:** send full ProjectConfig on env write (install.sh Phase 2 HTTP 422) ([#693](https://github.com/nanobpm/nano-workforce/issues/693)) ([cea5a13](https://github.com/nanobpm/nano-workforce/commit/cea5a133fe2e53299432e8f61b81c8eadbf9f1ed)), closes [nanobpm/nano-workforce#692](https://github.com/nanobpm/nano-workforce/issues/692) [#692](https://github.com/nanobpm/nano-workforce/issues/692)
|
|
12
|
+
|
|
1
13
|
## [0.172.0](https://github.com/nanobpm/nano-workforce/compare/v0.171.11...v0.172.0) (2026-09-01)
|
|
2
14
|
|
|
3
15
|
### Features
|
package/docs/mcp-runbook.md
CHANGED
|
@@ -24,6 +24,11 @@ MCP is a **third door**, not a replacement: `GET /app/api/agent` (the live guide
|
|
|
24
24
|
`GET /app/api/agent/skill` are unchanged for agents without MCP — see
|
|
25
25
|
[§5 Fallback](#5-fallback).
|
|
26
26
|
|
|
27
|
+
> **Served summary:** a running instance also exposes a nav-linked **"Connect over
|
|
28
|
+
> MCP"** console page (`pages/mcp.page.json`) with copyable config recipes rendered
|
|
29
|
+
> for that instance's own address. It is the short, always-reachable digest; this
|
|
30
|
+
> runbook is the deeper source of truth. Keep the two in sync.
|
|
31
|
+
|
|
27
32
|
## 1. One MCP server entry per instance
|
|
28
33
|
|
|
29
34
|
In `~/.copilot/mcp-config.json` (user-wide) or `.mcp.json` (repo-scoped):
|
|
@@ -122,16 +127,24 @@ door the UI's Cancel uses), never the record-desyncing engine-level `urban_debug
|
|
|
122
127
|
|
|
123
128
|
## 4. Guard posture
|
|
124
129
|
|
|
125
|
-
When `NANO_PR_WEBHOOK_SECRET` is **unset**,
|
|
126
|
-
projections, the operator guide) and mutations
|
|
127
|
-
operations, answering escalations) work
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
130
|
+
When `NANO_PR_WEBHOOK_SECRET` is **unset**, the app guard is off entirely: both reads
|
|
131
|
+
(status, instances, incidents, projections, the operator guide) and mutations
|
|
132
|
+
(cancel/retry/resolve, `start/*` operations, answering escalations) work with no
|
|
133
|
+
credential **from wherever this instance is reachable** — with `network.bind: "all"`
|
|
134
|
+
that is the LAN, not just loopback, so leave it unset only where that exposure is
|
|
135
|
+
acceptable. When it **is set**, the guard is not mutation-only: it also covers
|
|
136
|
+
reads, so read endpoints like `GET /app/api/agent` and `GET /app/api/version`
|
|
137
|
+
return `401` without the `x-hook-secret` header, just as guarded mutations do. It
|
|
138
|
+
is not blanket, though — a few doors stay intentionally unguarded even when the
|
|
139
|
+
secret is set (e.g. the declarative Save-to-library page action, which
|
|
140
|
+
structurally cannot attach the header). Put it in the server entry's
|
|
131
141
|
`headers`, never in chat. For a remote fleet,
|
|
132
|
-
`NANO_WORKFORCE_BASE_URL` reachability rules apply unchanged
|
|
133
|
-
|
|
134
|
-
|
|
142
|
+
`NANO_WORKFORCE_BASE_URL` reachability rules apply unchanged. The rest of the app's
|
|
143
|
+
HTTP surface follows the `network.bind` manifest setting, but the runtime-served
|
|
144
|
+
`/app/mcp` surface is an exception: it is **loopback-only by default** and refuses
|
|
145
|
+
non-loopback peers with a `403` even when `network.bind` is `"all"`, until you
|
|
146
|
+
*also* set `URBAN_MCP_ALLOW_REMOTE=true` (see `e2e/support/mcp-harness.ts`).
|
|
147
|
+
LAN/remote MCP clients therefore need that knob in addition to a wide bind.
|
|
135
148
|
|
|
136
149
|
### Framework mutation guard — `urban_debug_*` mutations need `x-hook-secret` too
|
|
137
150
|
|
package/install.sh
CHANGED
|
@@ -1124,6 +1124,25 @@ build_config_body() { # $1 = redact? ("redact" to mask the token)
|
|
|
1124
1124
|
"$_gh" "$(json_str "$CONSOLE_ORIGIN")" "$(json_str "$APPVIEW_BASE")" "$(json_str "${PR_REVIEW_PORT:-3000}")"
|
|
1125
1125
|
}
|
|
1126
1126
|
|
|
1127
|
+
# Merge our env object into the project's FULL ProjectConfig and print the
|
|
1128
|
+
# merged object. `PUT /console/api/projects/<name>/config` is a whole-object
|
|
1129
|
+
# replace: it deserializes the body into the complete ProjectConfig (which
|
|
1130
|
+
# requires `name`, `main`, `platforms`, `lang`, …), so a partial {"env":{…}}
|
|
1131
|
+
# body fails with HTTP 422 "missing field name" (nanobpm/nano-workforce#692).
|
|
1132
|
+
# We therefore fetch the live config and set its `.env` instead of hand-building
|
|
1133
|
+
# a minimal body. Values are passed via the environment (not argv) so quoting is
|
|
1134
|
+
# never an issue; node is a hard preflight requirement so it is always present.
|
|
1135
|
+
# Prints the merged JSON on success; returns non-zero (empty stdout) if either
|
|
1136
|
+
# input is not parseable.
|
|
1137
|
+
merge_config_env() { # $1 = current ProjectConfig JSON, $2 = {"env":{…}} JSON
|
|
1138
|
+
NWF_CFG_JSON=$1 NWF_ENV_JSON=$2 node -e '
|
|
1139
|
+
const cfg = JSON.parse(process.env.NWF_CFG_JSON);
|
|
1140
|
+
const env = JSON.parse(process.env.NWF_ENV_JSON).env || {};
|
|
1141
|
+
cfg.env = env;
|
|
1142
|
+
process.stdout.write(JSON.stringify(cfg));
|
|
1143
|
+
' 2>/dev/null
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1127
1146
|
# Step 9 — the console must be reachable before we mutate anything.
|
|
1128
1147
|
app_preflight() {
|
|
1129
1148
|
info "Preflighting the console at ${CONSOLE_ORIGIN}/console"
|
|
@@ -1245,18 +1264,39 @@ app_provision() {
|
|
|
1245
1264
|
esac
|
|
1246
1265
|
fi
|
|
1247
1266
|
|
|
1248
|
-
# 11.5 — write ProjectConfig.env (token redacted in dry-run output).
|
|
1267
|
+
# 11.5 — write ProjectConfig.env (token redacted in dry-run output). The
|
|
1268
|
+
# config endpoint is a whole-object replace, so we GET the current config and
|
|
1269
|
+
# merge our env into it; a partial {"env":{…}} body 422s (#692).
|
|
1249
1270
|
info "Configuring project '$PROJECT' (ProjectConfig.env)"
|
|
1250
|
-
|
|
1271
|
+
_env_body=$(build_config_body) || {
|
|
1251
1272
|
record_failure "app: invalid ProjectConfig.env value (control characters)"
|
|
1252
1273
|
return 1
|
|
1253
1274
|
}
|
|
1254
|
-
|
|
1275
|
+
_env_show=$(build_config_body redact) || {
|
|
1255
1276
|
record_failure "app: invalid ProjectConfig.env value (control characters)"
|
|
1256
1277
|
return 1
|
|
1257
1278
|
}
|
|
1258
|
-
|
|
1259
|
-
|
|
1279
|
+
if [ "$DRY_RUN" -eq 1 ]; then
|
|
1280
|
+
# No live config to fetch under --dry-run: show the env we would merge in.
|
|
1281
|
+
api PUT "/console/api/projects/${PROJECT}/config" "$_env_body" "$_env_show"
|
|
1282
|
+
else
|
|
1283
|
+
api GET "/console/api/projects/${PROJECT}/config"
|
|
1284
|
+
case "$API_STATUS" in
|
|
1285
|
+
2*) : ;;
|
|
1286
|
+
*) err "GET project config -> $(api_outcome)"
|
|
1287
|
+
[ -n "$API_BODY" ] && note "$API_BODY"
|
|
1288
|
+
record_failure "app: GET config $(api_outcome)"
|
|
1289
|
+
return 1 ;;
|
|
1290
|
+
esac
|
|
1291
|
+
_cur_cfg=$API_BODY
|
|
1292
|
+
_cfg_body=$(merge_config_env "$_cur_cfg" "$_env_body") || {
|
|
1293
|
+
err "could not merge env into the current ProjectConfig (unparseable config?)."
|
|
1294
|
+
[ -n "$_cur_cfg" ] && note "$_cur_cfg"
|
|
1295
|
+
record_failure "app: merge ProjectConfig.env"
|
|
1296
|
+
return 1
|
|
1297
|
+
}
|
|
1298
|
+
_cfg_show=$(merge_config_env "$_cur_cfg" "$_env_show") || _cfg_show=$_cfg_body
|
|
1299
|
+
api PUT "/console/api/projects/${PROJECT}/config" "$_cfg_body" "$_cfg_show"
|
|
1260
1300
|
case "$API_STATUS" in
|
|
1261
1301
|
2*) ok "config written (NANO_WORKFORCE_BASE_URL=${APPVIEW_BASE})" ;;
|
|
1262
1302
|
*) err "PUT project config -> $(api_outcome)"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nanobpm/nano-workforce",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.173.0",
|
|
4
4
|
"description": "Nano Workforce — an Agent Graph Orchestration application for Agentic SDLC: durable BPMN processes that coordinate a graph of AI agents across the software delivery lifecycle.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "main.ts",
|
package/pages/_nav.json
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
"title": "Nano Workforce",
|
|
7
7
|
"items": [
|
|
8
8
|
{ "label": "Overview", "page": "overview" },
|
|
9
|
+
{ "label": "Connect over MCP", "page": "mcp" },
|
|
9
10
|
{ "label": "Lineage", "page": "lineage" },
|
|
10
11
|
{ "label": "Convergence", "page": "home" },
|
|
11
12
|
{ "label": "Epics", "page": "epic" },
|
package/pages/board.page.json
CHANGED
package/pages/cockpit.page.json
CHANGED
package/pages/epic.page.json
CHANGED
package/pages/feature.page.json
CHANGED
package/pages/home.page.json
CHANGED
package/pages/lineage.page.json
CHANGED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "1.0",
|
|
3
|
+
"title": "Connect over MCP",
|
|
4
|
+
"nodes": [
|
|
5
|
+
{
|
|
6
|
+
"type": "nav",
|
|
7
|
+
"id": "nav",
|
|
8
|
+
"props": {
|
|
9
|
+
"variant": "bar",
|
|
10
|
+
"title": "Nano Workforce",
|
|
11
|
+
"items": [
|
|
12
|
+
{
|
|
13
|
+
"label": "Overview",
|
|
14
|
+
"page": "overview"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"label": "Connect over MCP",
|
|
18
|
+
"page": "mcp"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"label": "Lineage",
|
|
22
|
+
"page": "lineage"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"label": "Convergence",
|
|
26
|
+
"page": "home"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"label": "Epics",
|
|
30
|
+
"page": "epic"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"label": "Feature",
|
|
34
|
+
"page": "feature"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"label": "Delivery Graphs",
|
|
38
|
+
"page": "delivery-graphs"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"label": "Tasks",
|
|
42
|
+
"page": "tasks",
|
|
43
|
+
"badge": {
|
|
44
|
+
"source": "app",
|
|
45
|
+
"table": "user_tasks",
|
|
46
|
+
"filter": [],
|
|
47
|
+
"tone": "danger",
|
|
48
|
+
"refreshMs": 5000,
|
|
49
|
+
"hideWhenZero": true
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"label": "Cockpit",
|
|
54
|
+
"page": "cockpit"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"label": "Board",
|
|
58
|
+
"page": "board"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"label": "Velocity",
|
|
62
|
+
"page": "velocity"
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
"sticky": true
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"type": "text",
|
|
70
|
+
"id": "title",
|
|
71
|
+
"props": { "text": "Connect over MCP", "variant": "heading" }
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"type": "text",
|
|
75
|
+
"id": "intro",
|
|
76
|
+
"props": {
|
|
77
|
+
"text": "Point a coding agent (Copilot, Claude, Cursor \u2026) at this running instance so the workforce's operations become native tools \u2014 submit work, answer escalations, read status, and debug a wedged instance without curl. The Urban runtime serves a Streamable-HTTP MCP endpoint for this app at the /app/mcp path \u2014 behind a reverse-proxy prefix the reachable URL carries that prefix, as the recipes below render \u2014 and projects its openapi.yaml into tools, with zero MCP code in nwf. Register ONE server entry per instance; its tools are namespaced under the name you give it, so naming the instance targets the right one and makes the wrong-instance mistake very hard to hit (a server pointed at the wrong URL can still misfire). Use the copyable recipes below \u2014 each URL is already rendered for THIS instance's address (including any reverse-proxy prefix).",
|
|
78
|
+
"variant": "sub"
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"type": "button",
|
|
83
|
+
"id": "recipe-config",
|
|
84
|
+
"props": {
|
|
85
|
+
"label": "\ud83d\udccb Config recipe \u2014 local / LAN / remote (mcp-config.json)",
|
|
86
|
+
"variant": "ghost",
|
|
87
|
+
"modal": {
|
|
88
|
+
"title": "One server entry per instance",
|
|
89
|
+
"description": "Add to ~/.copilot/mcp-config.json (user-wide) or .mcp.json (repo-scoped). The workforce-local entry is pre-filled with THIS instance's URL; the merlin/remote entries show the LAN and ngrok shapes \u2014 give each node its own named entry. If THIS instance is guarded (NANO_PR_WEBHOOK_SECRET set), add the same \"headers\": { \"x-hook-secret\": \"$NANO_PR_WEBHOOK_SECRET\" } block to workforce-local too, or its calls 401. MCP servers register at host startup, so add the entry, THEN start a new session for its tools to load.",
|
|
90
|
+
"copyLabel": "Copy config",
|
|
91
|
+
"copyText": "{\n \"mcpServers\": {\n \"workforce-local\": {\n \"type\": \"http\",\n \"url\": \"{{appBase}}app/mcp\",\n \"tools\": [\"*\"]\n },\n \"workforce-merlin\": {\n \"type\": \"http\",\n \"url\": \"http://merlin.local:3000/app/mcp\",\n \"headers\": { \"x-hook-secret\": \"$NANO_PR_WEBHOOK_SECRET\" },\n \"tools\": [\"*\"]\n },\n \"workforce-remote\": {\n \"type\": \"http\",\n \"url\": \"https://<subdomain>.ngrok.app/app/mcp\",\n \"headers\": { \"x-hook-secret\": \"$NANO_PR_WEBHOOK_SECRET\" },\n \"tools\": [\"*\"]\n }\n }\n}"
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"type": "button",
|
|
97
|
+
"id": "recipe-cli",
|
|
98
|
+
"props": {
|
|
99
|
+
"label": "\ud83d\udccb CLI form (copilot mcp add)",
|
|
100
|
+
"variant": "ghost",
|
|
101
|
+
"modal": {
|
|
102
|
+
"title": "Add from the terminal",
|
|
103
|
+
"description": "For a guarded instance (NANO_PR_WEBHOOK_SECRET set), pass the shared-secret header with the --header form (the second command); an unguarded instance needs only the first.",
|
|
104
|
+
"copyLabel": "Copy commands",
|
|
105
|
+
"copyText": "# This instance if unguarded (no NANO_PR_WEBHOOK_SECRET set):\ncopilot mcp add --transport http workforce-local {{appBase}}app/mcp\n\n# This instance if guarded (NANO_PR_WEBHOOK_SECRET set) \u2014 present the shared secret header:\ncopilot mcp add --transport http workforce-local {{appBase}}app/mcp \\\n --header \"x-hook-secret: $NANO_PR_WEBHOOK_SECRET\""
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"type": "text",
|
|
111
|
+
"id": "secret-heading",
|
|
112
|
+
"props": { "text": "Shared-secret setup (x-hook-secret)", "variant": "heading" }
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"type": "text",
|
|
116
|
+
"id": "secret-body",
|
|
117
|
+
"props": {
|
|
118
|
+
"text": "When this instance sets NANO_PR_WEBHOOK_SECRET, the guard is NOT mutation-only \u2014 it also covers reads, so read endpoints like GET /app/api/agent and GET /app/api/version return 401 without the x-hook-secret header, just as guarded mutations do. It is not blanket, though: a few doors stay intentionally unguarded even when the secret is set (e.g. the declarative Save-to-library page action, which structurally cannot attach the header). The mcp-config.json path takes the shared secret as a headers block, not a flag, so add a headers block alongside url on the server entry (copy it below); omitting it yields 401s. Put the secret in the server entry's headers, never in chat. When NANO_PR_WEBHOOK_SECRET is unset, the app guard is off entirely \u2014 reads and mutations both work with no credential from wherever this instance is reachable (with network.bind \"all\" that is the LAN, not just loopback), so unset it only where that exposure is acceptable. Note the runtime-served /app/mcp surface is an exception to that reachability: it is loopback-only by default and refuses non-loopback peers with a 403 even when network.bind is \"all\", until you also set URBAN_MCP_ALLOW_REMOTE=true \u2014 LAN/remote MCP clients need that knob in addition to a wide bind.",
|
|
119
|
+
"variant": "sub"
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"type": "button",
|
|
124
|
+
"id": "recipe-secret",
|
|
125
|
+
"props": {
|
|
126
|
+
"label": "\ud83d\udccb Shared-secret header block",
|
|
127
|
+
"variant": "ghost",
|
|
128
|
+
"modal": {
|
|
129
|
+
"title": "Add the x-hook-secret header",
|
|
130
|
+
"description": "Drop this headers entry alongside url on the guarded server's config-form entry.",
|
|
131
|
+
"copyLabel": "Copy headers",
|
|
132
|
+
"copyText": "\"headers\": { \"x-hook-secret\": \"$NANO_PR_WEBHOOK_SECRET\" }"
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"type": "text",
|
|
138
|
+
"id": "basic-auth-heading",
|
|
139
|
+
"props": { "text": "Basic-Auth-fronted instances (reverse proxy)", "variant": "heading" }
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"type": "text",
|
|
143
|
+
"id": "basic-auth-body",
|
|
144
|
+
"props": {
|
|
145
|
+
"text": "These are two different layers. x-hook-secret is the app's own guard, checked by nwf. Basic Auth is enforced by whatever fronts the instance (ngrok edge, console proxy) and 401s before the request ever reaches nwf. A Basic-Auth-fronted instance therefore needs BOTH headers on the connection: Authorization: Basic \u2026 for the proxy AND x-hook-secret for the app. Generate the blob with printf '%s' 'user:pass' | base64 (echo appends a newline and yields the wrong value); Base64 is encoding, not encryption \u2014 only use Basic Auth over HTTPS.",
|
|
146
|
+
"variant": "sub"
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"type": "button",
|
|
151
|
+
"id": "recipe-basic-auth",
|
|
152
|
+
"props": {
|
|
153
|
+
"label": "\ud83d\udccb Basic-Auth + secret (both headers)",
|
|
154
|
+
"variant": "ghost",
|
|
155
|
+
"modal": {
|
|
156
|
+
"title": "Both headers, two different layers",
|
|
157
|
+
"description": "Proxy layer (Authorization) plus app layer (x-hook-secret) on the same server entry.",
|
|
158
|
+
"copyLabel": "Copy headers",
|
|
159
|
+
"copyText": "\"headers\": {\n \"Authorization\": \"Basic <base64(user:pass)>\",\n \"x-hook-secret\": \"$NANO_PR_WEBHOOK_SECRET\"\n}"
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"type": "text",
|
|
165
|
+
"id": "mutation-guard-heading",
|
|
166
|
+
"props": { "text": "urban_debug_* mutation-guard caveat", "variant": "heading" }
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"type": "text",
|
|
170
|
+
"id": "mutation-guard-body",
|
|
171
|
+
"props": {
|
|
172
|
+
"text": "The framework's mutating engine-debug tools (set_variables / retry_job / resolve_incident / cancel_instance) require the app's shared-secret scheme (or the loopback-only allowMutations opt-in when allowRemote is off). Until issue #698 declares x-nano-secret-env, remote mutations are refused on any allowRemote-on instance even with reads open \u2014 that is the current posture, tracked in #698. The read tools (urban_debug_search_process_instances / _element_instance_wait_states / _incidents, and where projected _jobs / _variables) work under the same x-hook-secret as the rest of the surface. Operator-only doors stay operator-only: the delivery-graph stage / dispatch / dismiss lifecycle is x-mcp-excluded from the tool surface \u2014 the human clicking Dispatch in the cockpit IS the approval \u2014 so an agent cannot dispatch a delivery graph through MCP (it authors graphs through the pure compileDeliveryGraph / previewDeliveryGraph doors, which stay exposed).",
|
|
173
|
+
"variant": "sub"
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
"type": "text",
|
|
178
|
+
"id": "fallback-heading",
|
|
179
|
+
"props": { "text": "Fallback \u2014 no MCP client", "variant": "heading" }
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
"type": "text",
|
|
183
|
+
"id": "fallback-body",
|
|
184
|
+
"props": {
|
|
185
|
+
"text": "Agents without an MCP client are unchanged \u2014 fetch and follow this instance's live operator guide over curl (the response is JSON with a skill markdown field). Add -H \"x-hook-secret: <secret>\" (and -u user:pass for a Basic-Auth-fronted instance) if this instance is guarded. That skill bootstraps you to the same live guide MCP exposes as the getAgentInstructions tool \u2014 or, over MCP, its addressable companion getAgentGuide(section?), which the runbook recommends over the ~43KB blob to avoid a tool-result overrun.",
|
|
186
|
+
"variant": "sub"
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
"type": "button",
|
|
191
|
+
"id": "recipe-fallback",
|
|
192
|
+
"props": {
|
|
193
|
+
"label": "\ud83d\udccb Fallback curl (no MCP client)",
|
|
194
|
+
"variant": "ghost",
|
|
195
|
+
"modal": {
|
|
196
|
+
"title": "The curl door is unchanged",
|
|
197
|
+
"description": "Fetch this instance's live guide directly; add the header(s) if guarded.",
|
|
198
|
+
"copyLabel": "Copy curl",
|
|
199
|
+
"copyText": "curl -sS {{appBase}}app/api/agent/skill\n# guarded instance \u2014 add the shared secret (and Basic Auth if fronted):\ncurl -sS {{appBase}}app/api/agent/skill \\\n -H \"x-hook-secret: <secret>\""
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"type": "text",
|
|
205
|
+
"id": "runbook-heading",
|
|
206
|
+
"props": { "text": "Deeper reference", "variant": "heading" }
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
"type": "text",
|
|
210
|
+
"id": "runbook-body",
|
|
211
|
+
"props": {
|
|
212
|
+
"text": "This served page is the short, always-reachable summary. The full runbook \u2014 discovery, debugging a wedged instance, guard posture, the projected-tool-schema notes and the regression harness \u2014 lives in the repo at docs/mcp-runbook.md (https://github.com/nanobpm/nano-workforce/blob/main/docs/mcp-runbook.md), with README.md \u00a7\"Configure an agent over MCP\" as its companion. Keep the two in sync: the runbook is the source of truth, this page is the served digest.",
|
|
213
|
+
"variant": "sub"
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
]
|
|
217
|
+
}
|
package/pages/overview.page.json
CHANGED
package/pages/tasks.page.json
CHANGED
|
@@ -9,12 +9,34 @@
|
|
|
9
9
|
"variant": "bar",
|
|
10
10
|
"title": "Nano Workforce",
|
|
11
11
|
"items": [
|
|
12
|
-
{
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
{
|
|
17
|
-
|
|
12
|
+
{
|
|
13
|
+
"label": "Overview",
|
|
14
|
+
"page": "overview"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"label": "Connect over MCP",
|
|
18
|
+
"page": "mcp"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"label": "Lineage",
|
|
22
|
+
"page": "lineage"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"label": "Convergence",
|
|
26
|
+
"page": "home"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"label": "Epics",
|
|
30
|
+
"page": "epic"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"label": "Feature",
|
|
34
|
+
"page": "feature"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"label": "Delivery Graphs",
|
|
38
|
+
"page": "delivery-graphs"
|
|
39
|
+
},
|
|
18
40
|
{
|
|
19
41
|
"label": "Tasks",
|
|
20
42
|
"page": "tasks",
|
|
@@ -27,9 +49,18 @@
|
|
|
27
49
|
"hideWhenZero": true
|
|
28
50
|
}
|
|
29
51
|
},
|
|
30
|
-
{
|
|
31
|
-
|
|
32
|
-
|
|
52
|
+
{
|
|
53
|
+
"label": "Cockpit",
|
|
54
|
+
"page": "cockpit"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"label": "Board",
|
|
58
|
+
"page": "board"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"label": "Velocity",
|
|
62
|
+
"page": "velocity"
|
|
63
|
+
}
|
|
33
64
|
],
|
|
34
65
|
"sticky": true
|
|
35
66
|
}
|
package/pages/velocity.page.json
CHANGED
package/test/install-smoke.sh
CHANGED
|
@@ -302,6 +302,13 @@ const srv = http.createServer((req, res) => {
|
|
|
302
302
|
if (m === 'POST' && url === '/console/api/extensions/install') return json(201, { id: 'nano-workforce' });
|
|
303
303
|
if (m === 'POST' && url === '/console/api/projects') return mode === 'exists' ? json(409, { error: 'exists' }) : json(201, { name: 'Workforce' });
|
|
304
304
|
if (m === 'PUT' && url === '/console/api/projects/Workforce/config') return json(200, { ok: true });
|
|
305
|
+
if (m === 'GET' && url === '/console/api/projects/Workforce/config') {
|
|
306
|
+
// The whole-object ProjectConfig install.sh fetches before merging `.env`
|
|
307
|
+
// and PUTting it back (nanobpm/nano-workforce#692). Must carry the required
|
|
308
|
+
// top-level fields (name, main, platforms, lang) so merge_config_env has a
|
|
309
|
+
// full object to set `.env` on.
|
|
310
|
+
return json(200, { name: 'Workforce', main: 'nano-workforce', platforms: ['console'], lang: 'ts', env: {} });
|
|
311
|
+
}
|
|
305
312
|
if (m === 'POST' && url === '/console/api/projects/Workforce/run') { ran = true; return json(200, { state: 'running' }); }
|
|
306
313
|
if (m === 'GET' && url === '/console/app-view/Workforce/app/api/version') {
|
|
307
314
|
if (mode === 'timeout') return json(404, {});
|