crewly 1.20.250 → 1.20.252
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/config/skills/agent/core/app-comments/SKILL.md +11 -1
- package/config/skills/agent/core/app-data/SKILL.md +65 -3
- package/config/skills/agent/core/app-data/execute.sh +51 -3
- package/config/skills/agent/core/app-data/execute.test.sh +42 -0
- package/config/skills/agent/core/complete-task/SKILL.md +5 -1
- package/config/skills/agent/core/complete-task/execute.sh +30 -15
- package/config/skills/agent/core/complete-task/execute.test.sh +45 -1
- package/config/skills/agent/core/publish-app/SKILL.md +5 -1
- package/config/skills/agent/core/publish-app/execute.sh +26 -3
- package/config/skills/agent/core/publish-app/execute.test.sh +18 -0
- package/dist/backend/backend/src/constants.d.ts +66 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +66 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/apps/apps.controller.d.ts +17 -0
- package/dist/backend/backend/src/controllers/apps/apps.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/apps/apps.controller.js +31 -0
- package/dist/backend/backend/src/controllers/apps/apps.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/apps/apps.routes.d.ts +7 -0
- package/dist/backend/backend/src/controllers/apps/apps.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/apps/apps.routes.js +29 -2
- package/dist/backend/backend/src/controllers/apps/apps.routes.js.map +1 -1
- package/dist/backend/backend/src/controllers/team/team.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/team/team.controller.js +43 -4
- package/dist/backend/backend/src/controllers/team/team.controller.js.map +1 -1
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +13 -0
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/services/agent/agent-registration.service.js +2 -2
- package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/resource-mode.service.d.ts +13 -0
- package/dist/backend/backend/src/services/agent/resource-mode.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/resource-mode.service.js +36 -0
- package/dist/backend/backend/src/services/agent/resource-mode.service.js.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/dist/backend/backend/src/services/apps/app-collaborators.service.d.ts +103 -0
- package/dist/backend/backend/src/services/apps/app-collaborators.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/apps/app-collaborators.service.js +161 -0
- package/dist/backend/backend/src/services/apps/app-collaborators.service.js.map +1 -0
- package/dist/backend/backend/src/services/apps/app-comment-room.service.d.ts +149 -0
- package/dist/backend/backend/src/services/apps/app-comment-room.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/apps/app-comment-room.service.js +316 -0
- package/dist/backend/backend/src/services/apps/app-comment-room.service.js.map +1 -0
- package/dist/backend/backend/src/services/apps/app-comments-slack.service.d.ts +38 -1
- package/dist/backend/backend/src/services/apps/app-comments-slack.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/apps/app-comments-slack.service.js +58 -3
- package/dist/backend/backend/src/services/apps/app-comments-slack.service.js.map +1 -1
- package/dist/backend/backend/src/services/apps/app-roster.service.d.ts +37 -1
- package/dist/backend/backend/src/services/apps/app-roster.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/apps/app-roster.service.js +62 -7
- package/dist/backend/backend/src/services/apps/app-roster.service.js.map +1 -1
- package/dist/backend/backend/src/services/apps/app-wake-message.d.ts +6 -0
- package/dist/backend/backend/src/services/apps/app-wake-message.d.ts.map +1 -1
- package/dist/backend/backend/src/services/apps/app-wake-message.js +12 -10
- package/dist/backend/backend/src/services/apps/app-wake-message.js.map +1 -1
- package/dist/backend/backend/src/services/apps/app-wake.service.d.ts +30 -0
- package/dist/backend/backend/src/services/apps/app-wake.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/apps/app-wake.service.js +90 -16
- package/dist/backend/backend/src/services/apps/app-wake.service.js.map +1 -1
- package/dist/backend/backend/src/services/apps/apps-cloud.client.d.ts +6 -0
- package/dist/backend/backend/src/services/apps/apps-cloud.client.d.ts.map +1 -1
- package/dist/backend/backend/src/services/apps/apps-cloud.client.js +5 -4
- package/dist/backend/backend/src/services/apps/apps-cloud.client.js.map +1 -1
- package/dist/backend/backend/src/services/apps/apps.service.d.ts +115 -2
- package/dist/backend/backend/src/services/apps/apps.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/apps/apps.service.js +181 -9
- package/dist/backend/backend/src/services/apps/apps.service.js.map +1 -1
- package/dist/backend/backend/src/services/apps/apps.wiring.d.ts +57 -1
- package/dist/backend/backend/src/services/apps/apps.wiring.d.ts.map +1 -1
- package/dist/backend/backend/src/services/apps/apps.wiring.js +233 -4
- package/dist/backend/backend/src/services/apps/apps.wiring.js.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.service.d.ts +1 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.service.js +3 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.service.js.map +1 -1
- package/dist/backend/backend/src/services/decisions/decision.service.d.ts +2 -0
- package/dist/backend/backend/src/services/decisions/decision.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/decisions/decision.service.js +1 -0
- package/dist/backend/backend/src/services/decisions/decision.service.js.map +1 -1
- package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.d.ts +24 -1
- package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.js +44 -1
- package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.js.map +1 -1
- package/dist/backend/backend/src/services/orchestrator/commitment-approval-guard.d.ts +48 -0
- package/dist/backend/backend/src/services/orchestrator/commitment-approval-guard.d.ts.map +1 -1
- package/dist/backend/backend/src/services/orchestrator/commitment-approval-guard.js +46 -0
- package/dist/backend/backend/src/services/orchestrator/commitment-approval-guard.js.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.d.ts +43 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.d.ts.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.js +34 -0
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.js.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-stats.d.ts.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-stats.js +1 -0
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-stats.js.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.js +23 -7
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.js.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconcile-rules.d.ts +5 -0
- package/dist/backend/backend/src/services/reconciler/reconcile-rules.d.ts.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconcile-rules.js.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts +18 -2
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js +67 -8
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconciler.service.d.ts +117 -3
- package/dist/backend/backend/src/services/reconciler/reconciler.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconciler.service.js +304 -19
- package/dist/backend/backend/src/services/reconciler/reconciler.service.js.map +1 -1
- package/dist/backend/backend/src/services/reconciler/stalled-agent-recovery.d.ts +66 -0
- package/dist/backend/backend/src/services/reconciler/stalled-agent-recovery.d.ts.map +1 -0
- package/dist/backend/backend/src/services/reconciler/stalled-agent-recovery.js +107 -0
- package/dist/backend/backend/src/services/reconciler/stalled-agent-recovery.js.map +1 -0
- package/dist/backend/backend/src/services/trace/trace-metrics.d.ts.map +1 -1
- package/dist/backend/backend/src/services/trace/trace-metrics.js +2 -1
- package/dist/backend/backend/src/services/trace/trace-metrics.js.map +1 -1
- package/dist/backend/backend/src/services/trace/trace.types.d.ts +1 -1
- package/dist/backend/backend/src/services/trace/trace.types.d.ts.map +1 -1
- package/dist/backend/backend/src/services/trace/trace.types.js +2 -0
- package/dist/backend/backend/src/services/trace/trace.types.js.map +1 -1
- package/dist/backend/backend/src/services/v3/workitem-dispatch.subscriber.d.ts +15 -0
- package/dist/backend/backend/src/services/v3/workitem-dispatch.subscriber.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/workitem-dispatch.subscriber.js +47 -0
- package/dist/backend/backend/src/services/v3/workitem-dispatch.subscriber.js.map +1 -1
- package/dist/backend/backend/src/types/decision.types.d.ts +27 -2
- package/dist/backend/backend/src/types/decision.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/v2/index.d.ts +1 -1
- package/dist/backend/backend/src/types/v2/index.d.ts.map +1 -1
- package/dist/backend/backend/src/types/v2/index.js.map +1 -1
- package/dist/backend/backend/src/types/v2/reconcile.types.d.ts +20 -0
- package/dist/backend/backend/src/types/v2/reconcile.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/v2/reconcile.types.js.map +1 -1
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +66 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +66 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/agent/agent-registration.service.js +2 -2
- package/dist/cli/backend/src/services/agent/agent-registration.service.js.map +1 -1
- package/dist/cli/backend/src/services/agent/resource-mode.service.d.ts +13 -0
- package/dist/cli/backend/src/services/agent/resource-mode.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/resource-mode.service.js +36 -0
- package/dist/cli/backend/src/services/agent/resource-mode.service.js.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/dist/cli/backend/src/services/chat-v2/chat-v2.service.d.ts +1 -1
- package/dist/cli/backend/src/services/chat-v2/chat-v2.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/chat-v2/chat-v2.service.js +3 -0
- package/dist/cli/backend/src/services/chat-v2/chat-v2.service.js.map +1 -1
- package/dist/cli/backend/src/services/decisions/decision.service.d.ts +2 -0
- package/dist/cli/backend/src/services/decisions/decision.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/decisions/decision.service.js +1 -0
- package/dist/cli/backend/src/services/decisions/decision.service.js.map +1 -1
- package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.d.ts +24 -1
- package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.js +44 -1
- package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.js.map +1 -1
- package/dist/cli/backend/src/services/orchestrator/commitment-approval-guard.d.ts +48 -0
- package/dist/cli/backend/src/services/orchestrator/commitment-approval-guard.d.ts.map +1 -1
- package/dist/cli/backend/src/services/orchestrator/commitment-approval-guard.js +46 -0
- package/dist/cli/backend/src/services/orchestrator/commitment-approval-guard.js.map +1 -1
- package/dist/cli/backend/src/services/trace/trace.types.d.ts +1 -1
- package/dist/cli/backend/src/services/trace/trace.types.d.ts.map +1 -1
- package/dist/cli/backend/src/services/trace/trace.types.js +2 -0
- package/dist/cli/backend/src/services/trace/trace.types.js.map +1 -1
- package/dist/cli/backend/src/types/decision.types.d.ts +27 -2
- package/dist/cli/backend/src/types/decision.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/v2/index.d.ts +1 -1
- package/dist/cli/backend/src/types/v2/index.d.ts.map +1 -1
- package/dist/cli/backend/src/types/v2/index.js.map +1 -1
- package/dist/cli/backend/src/types/v2/reconcile.types.d.ts +20 -0
- package/dist/cli/backend/src/types/v2/reconcile.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/v2/reconcile.types.js.map +1 -1
- package/frontend/dist/assets/{index-2db17f1e.js → index-83188f0b.js} +2 -2
- package/frontend/dist/index.html +1 -1
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: App Comments
|
|
3
3
|
description: The owner's comments on a Crewly App — each one points at an element they tapped in the app (data-crewly-id, CSS selector, text, outerHTML, position). List them, reply in the thread, resolve after you addressed one, reopen. Use it when an [APP CHANGES] message says the owner commented on your app or @mentioned you in a comment.
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
category: productivity
|
|
6
6
|
skillType: claude-skill
|
|
7
7
|
assignableRoles:
|
|
@@ -64,6 +64,16 @@ Output (`--list`):
|
|
|
64
64
|
"comment":"Make this green","at":"…","replies":[],"resolvedBy":null}]}
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
+
## Apps owned by a team or a channel
|
|
68
|
+
|
|
69
|
+
If the app's owner is a team or a Crewly channel (`app-data --owner`), each
|
|
70
|
+
comment arrives as a thread in that room (and its Slack channel), not as an
|
|
71
|
+
`[APP CHANGES]` message: "💬 Comment on Daily brief (#3) — Heading “Today”: …".
|
|
72
|
+
The room's rules decide who answers. Reply **in that thread** (as for any room
|
|
73
|
+
message): your reply is added to the comment in the app, so do not also run
|
|
74
|
+
`--reply`. Use `--resolve <id>` when it is done; that shows in the thread too.
|
|
75
|
+
The owner's replies in the thread (in Slack or in the app) reach the room once.
|
|
76
|
+
|
|
67
77
|
## Finding the element
|
|
68
78
|
|
|
69
79
|
`on` is what the app's page reported for the tapped element:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: App Data
|
|
3
3
|
description: Read and write the data of a Crewly App you published (the same collections the app's page sees through crewly.db) — list, get, set, update, add, delete. Use it to fill an app with content, act on what the owner entered, or answer an [APP CHANGES] message.
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.4.0
|
|
5
5
|
category: productivity
|
|
6
6
|
skillType: claude-skill
|
|
7
7
|
assignableRoles:
|
|
@@ -44,6 +44,11 @@ bash execute.sh --app 28au74d9cj --set meta settings --data '{"title":"Weekly sh
|
|
|
44
44
|
bash execute.sh --app 28au74d9cj --update items 1kx2-ab --data '{"done":true}' --if-rev 3
|
|
45
45
|
bash execute.sh --app 28au74d9cj --add items --data '{"name":"eggs","done":false}'
|
|
46
46
|
bash execute.sh --app 28au74d9cj --delete items 1kx2-ab
|
|
47
|
+
bash execute.sh --app 28au74d9cj --request-access --reason "write the morning briefing" # ask the owner, see "Collaborators"
|
|
48
|
+
bash execute.sh --app 28au74d9cj --collaborators # who the owner let in
|
|
49
|
+
bash execute.sh --app 28au74d9cj --owner # who the app's comments go to
|
|
50
|
+
bash execute.sh --app 28au74d9cj --set-owner 'channel:#daily-brief' # see "Owner"
|
|
51
|
+
bash execute.sh --app 28au74d9cj --add-collaborator Kai # owner agents only
|
|
47
52
|
```
|
|
48
53
|
|
|
49
54
|
Output:
|
|
@@ -53,8 +58,9 @@ Output:
|
|
|
53
58
|
{"success":true,"id":"settings","data":{"title":"Weekly shop"},"rev":1,"updatedAt":"…"}
|
|
54
59
|
```
|
|
55
60
|
|
|
56
|
-
- You can use the data of apps you published
|
|
57
|
-
(`not_your_app`
|
|
61
|
+
- You can use the data of apps you published, that a teammate published, or
|
|
62
|
+
that the owner added you (or your team) to as a collaborator (`not_your_app`
|
|
63
|
+
otherwise).
|
|
58
64
|
- `--data-file <path>` reads the JSON from a regular file inside your project
|
|
59
65
|
directory (not a symlink, not under `~/.crewly`, at most 1 MB).
|
|
60
66
|
- `--list` pages with `--after <next>` until `next` is null.
|
|
@@ -64,6 +70,62 @@ Output:
|
|
|
64
70
|
- The app's page sees your writes at once (its subscriptions fire). Your
|
|
65
71
|
writes never wake you; the owner's do (`[APP CHANGES]`).
|
|
66
72
|
|
|
73
|
+
## Collaborators: working in an app another team published
|
|
74
|
+
|
|
75
|
+
If the app is another team's, you get `not_your_app`. Do not publish a copy.
|
|
76
|
+
Ask the owner:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
bash execute.sh --app <appId> --request-access --reason "what you need to write"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
- The owner gets one card, **Allow / Do not allow**. Only their tap grants
|
|
83
|
+
anything; you cannot add yourself, and nothing you send changes who is added
|
|
84
|
+
(it is your own team, or only you with `--scope agent`).
|
|
85
|
+
- You are told when they answer. With **Allow** your team (and teammates who join
|
|
86
|
+
later) can use that app's **data and comments** with this skill, using the app
|
|
87
|
+
id. You get the app id from the owner or the publisher; collaborators do not
|
|
88
|
+
show up in `publish-app`'s list.
|
|
89
|
+
- It is **data only**: you cannot republish, roll back, transfer or change who
|
|
90
|
+
can open the app (`publish-app --app <id>` is refused). To change the page
|
|
91
|
+
itself, ask the publisher.
|
|
92
|
+
- The owner can take the access away at any time; your next call then returns
|
|
93
|
+
`not_your_app`.
|
|
94
|
+
- `--list` shows who wrote each document: documents written by an agent carry
|
|
95
|
+
`by` (its session), e.g. `crewly-marketing-ella-…`.
|
|
96
|
+
|
|
97
|
+
## Owner: who the app's comments go to
|
|
98
|
+
|
|
99
|
+
Every app has an **owner**: one agent, a team, or a Crewly channel (a
|
|
100
|
+
cross-team room, see `list-channels`). By default it is the agent that
|
|
101
|
+
published it. The owner's comments in the app go to the owner:
|
|
102
|
+
|
|
103
|
+
- agent → that agent, mirrored to its Slack DM with the owner (as before);
|
|
104
|
+
- team → the team's room (its Slack team channel); the room's rules decide
|
|
105
|
+
who answers (an @mention in the comment → that agent; otherwise whoever is
|
|
106
|
+
awake reads it, and the lead when nobody is);
|
|
107
|
+
- channel → that channel, so its members from every team see the owner's
|
|
108
|
+
comments and any of them can pick one up.
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
bash execute.sh --app <appId> --owner # {"owner":{"kind":"agent","name":"Ella","default":true}}
|
|
112
|
+
bash execute.sh --app <appId> --set-owner 'channel:#daily-brief' # or 'team:Dev', 'agent:Kai', default
|
|
113
|
+
bash execute.sh --app <appId> --add-collaborator Kai # let another agent of this machine use the data
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
- Only the app's **owner agents** may change the owner or add a collaborator:
|
|
117
|
+
the owning agent, or a member of the owning team / channel (by default the
|
|
118
|
+
publisher). Anyone else gets `forbidden`; ask the owner, or use
|
|
119
|
+
`--request-access` for yourself.
|
|
120
|
+
- Members of the owning team or channel can use the app's data and comments
|
|
121
|
+
without being added. A collaborator gets data and comments only, never
|
|
122
|
+
publishing (that stays with the publisher's team).
|
|
123
|
+
- In a room, answer in the comment's thread: your reply there is added to the
|
|
124
|
+
comment in the app (do not also use `app-comments --reply`). Resolve with
|
|
125
|
+
`app-comments --resolve <id>` when it is done.
|
|
126
|
+
- The channel or team must exist on this machine (a channel made a moment ago
|
|
127
|
+
is fine).
|
|
128
|
+
|
|
67
129
|
## What you read is a sanitised display copy
|
|
68
130
|
|
|
69
131
|
Every string you read here — values **and** object keys, at any depth — is
|
|
@@ -19,6 +19,11 @@ Usage:
|
|
|
19
19
|
bash execute.sh --app <appId> --update <collection> <docId> --data '{"done":true}' [--if-rev 4]
|
|
20
20
|
bash execute.sh --app <appId> --add <collection> --data '{"name":"milk"}'
|
|
21
21
|
bash execute.sh --app <appId> --delete <collection> <docId>
|
|
22
|
+
bash execute.sh --app <appId> --request-access [--scope team|agent] [--reason "why"]
|
|
23
|
+
bash execute.sh --app <appId> --collaborators
|
|
24
|
+
bash execute.sh --app <appId> --owner
|
|
25
|
+
bash execute.sh --app <appId> --set-owner 'channel:#daily-brief' # or 'team:<name>', 'agent:<name>', default
|
|
26
|
+
bash execute.sh --app <appId> --add-collaborator <agent name or session>
|
|
22
27
|
|
|
23
28
|
Options:
|
|
24
29
|
--app App id (from publish-app)
|
|
@@ -26,6 +31,13 @@ Options:
|
|
|
26
31
|
--if-rev Only update when the doc is still at this rev (409 conflict otherwise)
|
|
27
32
|
--limit Page size for --list (1-500, default 100)
|
|
28
33
|
--after Continue a --list after this doc id (the previous page's "next")
|
|
34
|
+
--request-access Ask the owner to let your team (default) or only you work in an app another team published; a card goes to the owner, nothing is granted until they tap Allow
|
|
35
|
+
--collaborators Who the owner let work in the app
|
|
36
|
+
--scope team (your team, default) or agent (only you), with --request-access
|
|
37
|
+
--reason One line for the owner, with --request-access
|
|
38
|
+
--owner Show who owns the app's comments (an agent, a team or a channel)
|
|
39
|
+
--set-owner Change it: channel:#<name>, team:<name>, agent:<name> or default (the publishing agent). Only the app's owner agents (and the owner) may
|
|
40
|
+
--add-collaborator An owner agent lets another agent of this machine work in the app's data
|
|
29
41
|
--help | -h Show this help
|
|
30
42
|
EOF_USAGE
|
|
31
43
|
}
|
|
@@ -69,7 +81,7 @@ process.stdout.write(fs.readFileSync(r, 'utf8'));
|
|
|
69
81
|
NODE
|
|
70
82
|
}
|
|
71
83
|
|
|
72
|
-
APP=""; OP=""; COLL=""; DOC=""; DATA=""; IF_REV=""; LIMIT=""; AFTER=""
|
|
84
|
+
APP=""; OP=""; COLL=""; DOC=""; DATA=""; IF_REV=""; LIMIT=""; AFTER=""; SCOPE=""; REASON=""; SET_OWNER=""; WHO=""
|
|
73
85
|
while [[ $# -gt 0 ]]; do
|
|
74
86
|
case "$1" in
|
|
75
87
|
--app) [ $# -ge 2 ] || error_exit "--app requires a value"; APP="$2"; shift 2 ;;
|
|
@@ -84,18 +96,54 @@ while [[ $# -gt 0 ]]; do
|
|
|
84
96
|
--if-rev) [ $# -ge 2 ] || error_exit "--if-rev requires a value"; IF_REV="$2"; shift 2 ;;
|
|
85
97
|
--limit) [ $# -ge 2 ] || error_exit "--limit requires a value"; LIMIT="$2"; shift 2 ;;
|
|
86
98
|
--after) [ $# -ge 2 ] || error_exit "--after requires a value"; AFTER="$2"; shift 2 ;;
|
|
99
|
+
--request-access) OP="request-access"; shift ;;
|
|
100
|
+
--collaborators) OP="collaborators"; shift ;;
|
|
101
|
+
--owner) OP="owner"; shift ;;
|
|
102
|
+
--set-owner) [ $# -ge 2 ] || error_exit "--set-owner requires an owner (channel:#name, team:<name>, agent:<name> or default)"; OP="owner"; SET_OWNER="$2"; shift 2 ;;
|
|
103
|
+
--add-collaborator) [ $# -ge 2 ] || error_exit "--add-collaborator requires an agent name or session"; OP="add-collaborator"; WHO="$2"; shift 2 ;;
|
|
104
|
+
--scope) [ $# -ge 2 ] || error_exit "--scope requires a value"; SCOPE="$2"; shift 2 ;;
|
|
105
|
+
--reason) [ $# -ge 2 ] || error_exit "--reason requires a value"; REASON="$2"; shift 2 ;;
|
|
87
106
|
--help|-h) print_usage; exit 0 ;;
|
|
88
107
|
*) error_exit "Unknown option: $1" ;;
|
|
89
108
|
esac
|
|
90
109
|
done
|
|
91
110
|
|
|
92
111
|
[ -n "$APP" ] || error_exit "--app is required"
|
|
93
|
-
[ -n "$OP" ] || error_exit "one of --list / --get / --set / --update / --add / --delete is required"
|
|
112
|
+
[ -n "$OP" ] || error_exit "one of --list / --get / --set / --update / --add / --delete / --request-access / --collaborators / --owner / --add-collaborator is required"
|
|
94
113
|
[[ "$APP" =~ ^[a-z0-9]{10}$ ]] || error_exit "--app must be a 10-character app id"
|
|
95
114
|
case "$COLL" in .|..) error_exit "collection cannot be . or .." ;; esac
|
|
96
115
|
case "$DOC" in .|..) error_exit "doc id cannot be . or .." ;; esac
|
|
97
116
|
BASE="/apps/${APP}/data/$(uri "$COLL")"
|
|
98
117
|
|
|
118
|
+
case "$OP" in
|
|
119
|
+
request-access)
|
|
120
|
+
case "$SCOPE" in ""|team|agent) ;; *) error_exit "--scope is team or agent" ;; esac
|
|
121
|
+
BODY=$(jq -cn --arg s "$SCOPE" --arg r "$REASON" '{} + (if $s != "" then {scope: $s} else {} end) + (if $r != "" then {reason: $r} else {} end)')
|
|
122
|
+
RESPONSE=$(call POST "/apps/${APP}/collaborators/request" "$BODY") || { printf '%s\n' "$RESPONSE"; exit 1; }
|
|
123
|
+
printf '%s' "$RESPONSE" | jq -c '{success: true, requested: .data.requested, for: .data.for, note: "The owner got a card. You are told here when they answer; until then you still cannot use this app."}'
|
|
124
|
+
exit 0
|
|
125
|
+
;;
|
|
126
|
+
collaborators)
|
|
127
|
+
RESPONSE=$(call GET "/apps/${APP}/collaborators") || { printf '%s\n' "$RESPONSE"; exit 1; }
|
|
128
|
+
printf '%s' "$RESPONSE" | jq -c '{success: true, collaborators: [.data.collaborators[]? | {id, kind, name, who}]}'
|
|
129
|
+
exit 0
|
|
130
|
+
;;
|
|
131
|
+
owner)
|
|
132
|
+
if [ -n "$SET_OWNER" ]; then
|
|
133
|
+
RESPONSE=$(call PUT "/apps/${APP}/owner" "$(jq -cn --arg o "$SET_OWNER" '{owner: $o}')") || { printf '%s\n' "$RESPONSE"; exit 1; }
|
|
134
|
+
else
|
|
135
|
+
RESPONSE=$(call GET "/apps/${APP}/owner") || { printf '%s\n' "$RESPONSE"; exit 1; }
|
|
136
|
+
fi
|
|
137
|
+
printf '%s' "$RESPONSE" | jq -c '{success: true, owner: (.data.owner | if . == null then null else {kind, name, default: (.explicit | not)} + (if .members then {members: .members} else {} end) end)}'
|
|
138
|
+
exit 0
|
|
139
|
+
;;
|
|
140
|
+
add-collaborator)
|
|
141
|
+
RESPONSE=$(call POST "/apps/${APP}/collaborators/agents" "$(jq -cn --arg a "$WHO" '{agent: $a}')") || { printf '%s\n' "$RESPONSE"; exit 1; }
|
|
142
|
+
printf '%s' "$RESPONSE" | jq -c '{success: true, collaborators: [.data.collaborators[]? | {id, kind, name, who}]}'
|
|
143
|
+
exit 0
|
|
144
|
+
;;
|
|
145
|
+
esac
|
|
146
|
+
|
|
99
147
|
need_data() {
|
|
100
148
|
[ -n "$DATA" ] || error_exit "--data is required for --$OP"
|
|
101
149
|
printf '%s' "$DATA" | jq -e 'type == "object"' >/dev/null 2>&1 || error_exit "--data must be a JSON object"
|
|
@@ -107,7 +155,7 @@ case "$OP" in
|
|
|
107
155
|
[ -n "$LIMIT" ] && Q="limit=$(uri "$LIMIT")"
|
|
108
156
|
[ -n "$AFTER" ] && Q="${Q:+$Q&}after=$(uri "$AFTER")"
|
|
109
157
|
RESPONSE=$(call GET "${BASE}${Q:+?$Q}") || { printf '%s\n' "$RESPONSE"; exit 1; }
|
|
110
|
-
printf '%s' "$RESPONSE" | jq -c '{success: true, docs: [.data.docs[]? | {id, data, rev, updatedAt, updatedBy: (.updatedBy.kind // null)}], next: .data.next}'
|
|
158
|
+
printf '%s' "$RESPONSE" | jq -c '{success: true, docs: [.data.docs[]? | {id, data, rev, updatedAt, updatedBy: (.updatedBy.kind // null)} + (if .updatedBy.kind == "agent" then {by: .updatedBy.id} else {} end)], next: .data.next}'
|
|
111
159
|
;;
|
|
112
160
|
get)
|
|
113
161
|
RESPONSE=$(call GET "${BASE}/$(uri "$DOC")") || { printf '%s\n' "$RESPONSE"; exit 1; }
|
|
@@ -26,6 +26,18 @@ class H(BaseHTTPRequestHandler):
|
|
|
26
26
|
self.wfile.write(json.dumps(obj).encode())
|
|
27
27
|
if self.path.endswith('/missing'):
|
|
28
28
|
return send(404, {'success': False, 'error': 'not_found', 'message': 'Document not found.', 'hint': 'No such app, document or version for this account.'})
|
|
29
|
+
if self.path.endswith('/collaborators') and method == 'GET':
|
|
30
|
+
return send(200, {'success': True, 'data': {'collaborators': [{'id': 'e1', 'kind': 'team', 'who': 'Marketing', 'name': 'Marketing', 'instanceId': 'i'}]}})
|
|
31
|
+
if self.path.endswith('/owner'):
|
|
32
|
+
if method == 'PUT':
|
|
33
|
+
return send(200, {'success': True, 'data': {'appId': 'x', 'owner': {'kind': 'channel', 'explicit': True, 'channelId': 'h1', 'name': '#daily-brief', 'instanceId': 'i', 'members': ['a', 'b']}, 'previous': None}})
|
|
34
|
+
return send(200, {'success': True, 'data': {'appId': 'x', 'owner': {'kind': 'agent', 'explicit': False, 'session': 'dev-ella', 'name': 'Ella', 'instanceId': 'i'}}})
|
|
35
|
+
if self.path.endswith('/collaborators/agents'):
|
|
36
|
+
return send(200, {'success': True, 'data': {'collaborators': [{'id': 'e2', 'kind': 'agent', 'who': 'crewly-dev-kai-1', 'name': 'Kai', 'instanceId': 'i'}]}})
|
|
37
|
+
if self.path.endswith('/collaborators/request'):
|
|
38
|
+
return send(200, {'success': True, 'data': {'requested': True, 'decisionId': 'D-1', 'for': 'the Marketing team'}})
|
|
39
|
+
if self.path.endswith('/byagent'):
|
|
40
|
+
return send(200, {'success': True, 'data': {'docs': [{'id': 'a', 'data': {}, 'rev': 1, 'updatedAt': 't', 'updatedBy': {'kind': 'agent', 'id': 'crewly-marketing-ella-1'}}], 'next': None}})
|
|
29
41
|
if (data or {}).get('ifRev') == 1:
|
|
30
42
|
return send(409, {'success': False, 'error': 'conflict', 'message': 'rev mismatch'})
|
|
31
43
|
if method == 'GET' and '?' in self.path or self.path.endswith('/items'):
|
|
@@ -104,5 +116,35 @@ check "data-file in Crewly home refused" "$(printf '%s' "$OUT" | grep -c "inside
|
|
|
104
116
|
OUT=$(run_err --app $A --get items .. || true)
|
|
105
117
|
check "doc id .. refused" "$(printf '%s' "$OUT" | grep -c 'cannot be')" "1"
|
|
106
118
|
|
|
119
|
+
# A write by an agent shows who (the session); an owner write does not carry an id.
|
|
120
|
+
OUT=$(run --app $A --list byagent)
|
|
121
|
+
check "list: agent write shows its session" "$(printf '%s' "$OUT" | jq -c '.docs[0] | {updatedBy, by}')" '{"updatedBy":"agent","by":"crewly-marketing-ella-1"}'
|
|
122
|
+
|
|
123
|
+
# Asking for access: POST .../collaborators/request with the scope and reason only.
|
|
124
|
+
OUT=$(run --app $A --request-access --scope agent --reason "write the briefing")
|
|
125
|
+
check "request-access: path" "$(jq -r '.method + " " + .path' "$STUB_LOG")" "POST /api/apps/$A/collaborators/request"
|
|
126
|
+
check "request-access: body" "$(jq -c .body "$STUB_LOG")" '{"scope":"agent","reason":"write the briefing"}'
|
|
127
|
+
check "request-access: output says requested" "$(printf '%s' "$OUT" | jq -c '{success, requested, for}')" '{"success":true,"requested":true,"for":"the Marketing team"}'
|
|
128
|
+
OUT=$(run_err --app $A --request-access --scope everyone || true)
|
|
129
|
+
check "request-access: bad scope refused" "$(printf '%s' "$OUT" | grep -c 'team or agent')" "1"
|
|
130
|
+
OUT=$(run --app $A --collaborators)
|
|
131
|
+
check "collaborators: output" "$OUT" '{"success":true,"collaborators":[{"id":"e1","kind":"team","name":"Marketing","who":"Marketing"}]}'
|
|
132
|
+
|
|
133
|
+
# App owner (crewly-services apps/SPEC.md §15).
|
|
134
|
+
OUT=$(run --app $A --owner)
|
|
135
|
+
check "owner: shows the default owner" "$OUT" '{"success":true,"owner":{"kind":"agent","name":"Ella","default":true}}'
|
|
136
|
+
check "owner: GET path" "$(jq -r '.method + " " + .path' "$STUB_LOG")" "GET /api/apps/$A/owner"
|
|
137
|
+
OUT=$(run --app $A --set-owner 'channel:#daily-brief')
|
|
138
|
+
check "set-owner: request" "$(jq -c '{method, path, body}' "$STUB_LOG")" "{\"method\":\"PUT\",\"path\":\"/api/apps/$A/owner\",\"body\":{\"owner\":\"channel:#daily-brief\"}}"
|
|
139
|
+
check "set-owner: output" "$OUT" '{"success":true,"owner":{"kind":"channel","name":"#daily-brief","default":false,"members":["a","b"]}}'
|
|
140
|
+
OUT=$(run --app $A --add-collaborator Kai)
|
|
141
|
+
check "add-collaborator: request" "$(jq -c '{method, path, body}' "$STUB_LOG")" "{\"method\":\"POST\",\"path\":\"/api/apps/$A/collaborators/agents\",\"body\":{\"agent\":\"Kai\"}}"
|
|
142
|
+
check "add-collaborator: output" "$(printf '%s' "$OUT" | jq -c '.collaborators[0].name')" '"Kai"'
|
|
143
|
+
OUT=$(run_err --app $A --set-owner || true)
|
|
144
|
+
check "set-owner needs a value" "$(printf '%s' "$OUT" | grep -c 'requires an owner')" "1"
|
|
145
|
+
# --set still means a data write.
|
|
146
|
+
run --app $A --set items milk --data '{"done":true}' >/dev/null
|
|
147
|
+
check "--set is still a data write" "$(jq -r '.method + " " + .path' "$STUB_LOG")" "PUT /api/apps/$A/data/items/milk"
|
|
148
|
+
|
|
107
149
|
echo "app-data: $PASS passed, $FAIL failed"
|
|
108
150
|
[ "$FAIL" -eq 0 ]
|
|
@@ -37,7 +37,7 @@ execution:
|
|
|
37
37
|
|
|
38
38
|
# Complete Task
|
|
39
39
|
|
|
40
|
-
Mark a task as complete with a summary of the work done. If the task has an output schema, provide structured output that will be validated against the schema.
|
|
40
|
+
Mark a task as complete with a summary of the work done. If the task has an output schema, provide structured output that will be validated against the schema. Unknown fields are refused, never silently dropped.
|
|
41
41
|
|
|
42
42
|
## Parameters
|
|
43
43
|
|
|
@@ -51,6 +51,10 @@ Mark a task as complete with a summary of the work done. If the task has an outp
|
|
|
51
51
|
| `output` | No | Structured output object (required if task has an output schema) |
|
|
52
52
|
| ~~`skipGates`~~ | **Rejected** | **Not supported.** This endpoint runs no quality gates, so there is nothing to skip. Passing it is a hard error. Use the `check-quality-gates` skill for gates. |
|
|
53
53
|
|
|
54
|
+
**Any other field is an error.** The skill accepts only the fields in this table plus `verdict`/`feedback` (reviews) and `taskId`/`artifacts`/`testResults`/`structured` (verification requests). An unknown field (for example `projectPath`, `skipGates`, `force`) stops the call before anything is sent, and the error names the field. Put extra results inside `output`.
|
|
55
|
+
|
|
56
|
+
On success the skill prints **one JSON line** that always names the `workItemId`, plus the server's reply (`success`, `message`, any `warning`).
|
|
57
|
+
|
|
54
58
|
### Which WorkItem gets completed
|
|
55
59
|
|
|
56
60
|
Resolution order:
|
|
@@ -19,27 +19,31 @@ INPUT=$(read_json_input "${1:-}")
|
|
|
19
19
|
# /api/task-pool/complete by hand. Same silent-friction class as the
|
|
20
20
|
# create-task `{workItem: ...}` wrapper: the skill rejected a request that
|
|
21
21
|
# was, by its own contract, correct.
|
|
22
|
+
if [ "$(printf '%s' "$INPUT" | jq -r 'type' 2>/dev/null || echo invalid)" != "object" ]; then
|
|
23
|
+
error_exit "complete-task input must be a JSON object, e.g. {\"workItemId\":\"wi-abc123\",\"sessionName\":\"dev-1\",\"summary\":\"...\"}"
|
|
24
|
+
fi
|
|
22
25
|
WORK_ITEM_ID=$(printf '%s' "$INPUT" | jq -r '.workItemId // empty')
|
|
23
26
|
ABSOLUTE_TASK_PATH=$(printf '%s' "$INPUT" | jq -r '.absoluteTaskPath // empty')
|
|
24
27
|
SESSION_NAME=$(printf '%s' "$INPUT" | jq -r '.sessionName // empty')
|
|
25
28
|
SUMMARY=$(printf '%s' "$INPUT" | jq -r '.summary // empty')
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
# It was previously parsed here and forwarded in the request body. Nothing ever
|
|
29
|
-
# read it: `completeItem` destructures only `{agentId, tokenUsage, result}`
|
|
30
|
-
# (task-pool.controller.ts), and `POST /task-pool/complete/:id` runs no quality
|
|
31
|
-
# gates at all — gates live behind `POST /quality-gates/check`, reached via the
|
|
32
|
-
# `check-quality-gates` skill. So the field promised to skip something this
|
|
33
|
-
# endpoint never runs.
|
|
29
|
+
# Unknown top-level fields are REJECTED, not ignored (CREW-267).
|
|
34
30
|
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
|
|
40
|
-
|
|
31
|
+
# `skipGates` was once parsed and forwarded, and nothing ever read it:
|
|
32
|
+
# `POST /task-pool/complete/:id` runs no quality gates (they live behind the
|
|
33
|
+
# `check-quality-gates` skill). On 2026-10-06 seven completions passed it,
|
|
34
|
+
# printed a bare `{}`, and looked closed when they were not. Accepting and
|
|
35
|
+
# ignoring a field is the worst disposition: the caller believes it did
|
|
36
|
+
# something, and nothing ever fails. So every field this skill does not use
|
|
37
|
+
# fails loudly, with its name, before any request is sent.
|
|
38
|
+
ALLOWED_FIELDS='["workItemId","absoluteTaskPath","sessionName","summary","output","evidence","verdict","feedback","taskId","artifacts","testResults","structured"]'
|
|
39
|
+
SKIP_GATES=$(printf '%s' "$INPUT" | jq -r 'has("skipGates")')
|
|
40
|
+
if [ "$SKIP_GATES" = "true" ]; then
|
|
41
41
|
error_exit "skipGates is not supported by complete-task and never was — it was accepted and silently discarded. POST /task-pool/complete runs no quality gates, so there is nothing here to skip. Quality gates live behind the 'check-quality-gates' skill (POST /quality-gates/check); run or skip them there. Remove skipGates from this call."
|
|
42
42
|
fi
|
|
43
|
+
UNKNOWN_FIELDS=$(printf '%s' "$INPUT" | jq -r --argjson allowed "$ALLOWED_FIELDS" '[keys[] | select(. as $k | $allowed | index($k) | not)] | join(", ")')
|
|
44
|
+
if [ -n "$UNKNOWN_FIELDS" ]; then
|
|
45
|
+
error_exit "complete-task does not accept: ${UNKNOWN_FIELDS}. Nothing was completed. Remove the field(s) and call again. Accepted fields: $(printf '%s' "$ALLOWED_FIELDS" | jq -r 'join(", ")'). Put structured results inside \"output\" and proof inside \"evidence\"."
|
|
46
|
+
fi
|
|
43
47
|
OUTPUT_JSON=$(printf '%s' "$INPUT" | jq -c '.output // empty')
|
|
44
48
|
# Evidence contract (#873): "done" needs evidence. Each entry is one of
|
|
45
49
|
# {"type":"artifact","path":"<existing file, relative to the project/worktree, or https URL>"}
|
|
@@ -168,7 +172,18 @@ BODY=$(jq -n \
|
|
|
168
172
|
}')
|
|
169
173
|
|
|
170
174
|
COMPLETE_RESPONSE=$(api_call POST "/task-pool/complete/${WORK_ITEM_ID}" "$BODY")
|
|
171
|
-
|
|
175
|
+
# Always one result line naming the WorkItem (CREW-267): a bare `{}` read as
|
|
176
|
+
# "closed" when nothing said so. A non-empty object is passed through with
|
|
177
|
+
# the id added; an empty or non-JSON body gets an explicit line instead.
|
|
178
|
+
RESULT_LINE=$(printf '%s' "$COMPLETE_RESPONSE" | jq -c --arg id "$WORK_ITEM_ID" '
|
|
179
|
+
if type == "object" and length > 0 then {workItemId: $id} + .
|
|
180
|
+
else {success: true, workItemId: $id, message: ("WorkItem " + $id + " completion was accepted, but the server sent no details. Check it with GET /api/task-pool/items/" + $id + ".")}
|
|
181
|
+
end' 2>/dev/null || true)
|
|
182
|
+
if [ -z "$RESULT_LINE" ]; then
|
|
183
|
+
RESULT_LINE=$(jq -nc --arg id "$WORK_ITEM_ID" --arg raw "$COMPLETE_RESPONSE" \
|
|
184
|
+
'{success: true, workItemId: $id, message: ("WorkItem " + $id + " completion was accepted; the server reply was not JSON."), raw: $raw}')
|
|
185
|
+
fi
|
|
186
|
+
printf '%s\n' "$RESULT_LINE"
|
|
172
187
|
# Warn-mode rollout (#873): the server accepts a completion without evidence
|
|
173
188
|
# for one release but says so. Repeat it on stderr so it is not missed.
|
|
174
189
|
EVIDENCE_WARNING=$(printf '%s' "$COMPLETE_RESPONSE" | jq -r '.warning // empty' 2>/dev/null || true)
|
|
@@ -233,7 +233,7 @@ echo "--- Finished-task summary stays out of long-term memory (#833) ---"
|
|
|
233
233
|
|
|
234
234
|
# The summary lives on the WorkItem and in task-history.json. Saving it as a
|
|
235
235
|
# project decision crowded real decisions out of recall.
|
|
236
|
-
run_skill '{"workItemId":"wi-833","sessionName":"dev-1","summary":"Shipped the importer"
|
|
236
|
+
run_skill '{"workItemId":"wi-833","sessionName":"dev-1","summary":"Shipped the importer"}'
|
|
237
237
|
assert_contains "completion still reaches the task pool" \
|
|
238
238
|
"POST http://stub.invalid/api/task-pool/complete/wi-833" "$REQUESTS"
|
|
239
239
|
assert_not_contains "no /memory/remember call on completion" "/memory/remember" "$REQUESTS"
|
|
@@ -267,6 +267,50 @@ STUB_COMPLETE_RESPONSE='{"success":true,"message":"WorkItem wi-plain completed",
|
|
|
267
267
|
run_skill '{"workItemId":"wi-plain","sessionName":"dev-1","summary":"Report written"}'
|
|
268
268
|
assert_contains "server warning is printed" '"warning": "Completed WITHOUT evidence' "$OUT"
|
|
269
269
|
|
|
270
|
+
echo ""
|
|
271
|
+
echo "--- Unknown fields fail loudly, before any request (CREW-267) ---"
|
|
272
|
+
|
|
273
|
+
run_skill '{"workItemId":"wi-u","sessionName":"dev-1","summary":"done","projectPath":"/proj"}'
|
|
274
|
+
assert_contains "an unknown field is refused" '"error"' "$OUT"
|
|
275
|
+
assert_contains "the refusal names the field" "projectPath" "$OUT"
|
|
276
|
+
assert_contains "the refusal says nothing was completed" "Nothing was completed" "$OUT"
|
|
277
|
+
assert_not_contains "no request is sent for an unknown field" "/task-pool/complete" "$REQUESTS"
|
|
278
|
+
|
|
279
|
+
run_skill '{"workItemId":"wi-u","sessionName":"dev-1","summary":"done","fallback":true,"force":1}'
|
|
280
|
+
assert_contains "every unknown field is named (1)" "fallback" "$OUT"
|
|
281
|
+
assert_contains "every unknown field is named (2)" "force" "$OUT"
|
|
282
|
+
assert_not_contains "no request is sent for several unknown fields" "/task-pool/complete" "$REQUESTS"
|
|
283
|
+
|
|
284
|
+
run_skill '{"workItemId":"wi-u","sessionName":"dev-1","summary":"done","skipGates":false}'
|
|
285
|
+
assert_contains "skipGates is refused even when false" "skipGates is not supported" "$OUT"
|
|
286
|
+
assert_not_contains "no request is sent for skipGates" "/task-pool/complete" "$REQUESTS"
|
|
287
|
+
|
|
288
|
+
run_skill '["not","an","object"]'
|
|
289
|
+
assert_contains "a non-object input is refused" "must be a JSON object" "$OUT"
|
|
290
|
+
|
|
291
|
+
run_skill '{"workItemId":"wi-ok","sessionName":"dev-1","summary":"done","output":{"x":1},"evidence":[],"verdict":"verified","taskId":"t","artifacts":[],"testResults":"ok","structured":false,"absoluteTaskPath":"/x"}'
|
|
292
|
+
assert_not_contains "every documented field is still accepted" "does not accept" "$OUT"
|
|
293
|
+
assert_contains "a call with every documented field completes" \
|
|
294
|
+
"POST http://stub.invalid/api/task-pool/complete/wi-ok" "$REQUESTS"
|
|
295
|
+
|
|
296
|
+
echo ""
|
|
297
|
+
echo "--- Success always prints one result line naming the WorkItem (CREW-267) ---"
|
|
298
|
+
|
|
299
|
+
STUB_COMPLETE_RESPONSE='{}' run_skill '{"workItemId":"wi-empty","sessionName":"dev-1","summary":"done"}'
|
|
300
|
+
BARE_LINES="$(printf '%s\n' "$OUT" | grep -cx '{}' || true)"
|
|
301
|
+
assert_contains "an empty server reply is not echoed as a bare {}" "0" "$BARE_LINES"
|
|
302
|
+
LAST_LINE="$(printf '%s' "$OUT" | tail -1)"
|
|
303
|
+
assert_contains "the result line names the WorkItem" '"workItemId":"wi-empty"' "$LAST_LINE"
|
|
304
|
+
assert_contains "the result line says the server sent no details" "no details" "$LAST_LINE"
|
|
305
|
+
|
|
306
|
+
STUB_COMPLETE_RESPONSE='{"success":true,"message":"WorkItem wi-full completed"}' \
|
|
307
|
+
run_skill '{"workItemId":"wi-full","sessionName":"dev-1","summary":"done"}'
|
|
308
|
+
LAST_LINE="$(printf '%s' "$OUT" | tail -1)"
|
|
309
|
+
assert_contains "the server reply is kept" '"message":"WorkItem wi-full completed"' "$LAST_LINE"
|
|
310
|
+
assert_contains "the server reply gains the WorkItem id" '"workItemId":"wi-full"' "$LAST_LINE"
|
|
311
|
+
LINE_COUNT="$(printf '%s\n' "$OUT" | grep -c '"workItemId":"wi-full"' || true)"
|
|
312
|
+
assert_contains "the result is a single line" "1" "$LINE_COUNT"
|
|
313
|
+
|
|
270
314
|
rm -rf "$STUB_DIR"
|
|
271
315
|
|
|
272
316
|
echo ""
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: Publish App
|
|
3
3
|
description: Publish a small web app (HTML/JS) you wrote to https://apps.crewlyai.com/<appId> so the owner can open it on their phone. Creates the app the first time, uploads a new version each time after (same app for the same directory), supports rollback, and can post an "Open app" card to the owner (a one-tap signed link, in your DM with them). Can ask the owner to make an app public (only the owner can approve it). The owner's edits, comments on elements, and anonymous visitors' submissions on a public app, come back to you as an [APP CHANGES] message.
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.5.0
|
|
5
5
|
category: productivity
|
|
6
6
|
skillType: claude-skill
|
|
7
7
|
assignableRoles:
|
|
@@ -48,6 +48,8 @@ bash execute.sh --html ./timer.html --name "Timer"
|
|
|
48
48
|
bash execute.sh --app 28au74d9cj --rollback 2 # back to version 2
|
|
49
49
|
bash execute.sh --app 28au74d9cj --versions
|
|
50
50
|
bash execute.sh --app 28au74d9cj --transfer-to edu-game-milo-13e8d3ca # hand the app to another agent / team
|
|
51
|
+
bash execute.sh --dir ./daily-brief --owner 'channel:#daily-brief' # publish, and send the owner's comments to a channel
|
|
52
|
+
bash execute.sh --app 28au74d9cj --owner 'team:Dev' # change only who gets the comments
|
|
51
53
|
bash execute.sh --list # apps published from this machine
|
|
52
54
|
bash execute.sh --app 28au74d9cj --share # send the owner a fresh one-tap link (no publish)
|
|
53
55
|
bash execute.sh --app 28au74d9cj --links # its open-links; --revoke-link <id> / --revoke-links
|
|
@@ -132,6 +134,8 @@ bash execute.sh --app 28au74d9cj --private # pr
|
|
|
132
134
|
shared keep working. So test a new version before publishing it to a public
|
|
133
135
|
app. Widening what is public (more `--public-read` / `--public-submit`
|
|
134
136
|
collections) still needs a new request the owner approves.
|
|
137
|
+
- Collaborators: an app another team published is not yours to republish. `--app <id>` from a team that did not publish it is refused (`not_your_app`), including when the owner added your team as a collaborator: collaborators get the app's **data and comments** only (use `app-data`; ask with `app-data --app <id> --request-access`). To change the page itself, ask the publisher or the owner to transfer the app (`--transfer-to`).
|
|
138
|
+
- Owner: `--owner channel:#<name> | team:<name> | agent:<name> | default` sets who the owner's **comments** go to (a channel's members from every team see them; see `app-data` → Owner). It does not change who publishes. Only the app's owner agents may change it (by default the publisher); with a publish the owner is set right after it, and an `ownerError` in the output means only that part failed.
|
|
135
139
|
- Transfer: `--app <appId> --transfer-to <session>` hands the app to another agent, e.g. when the owner moves the work to a new team. Only the app's publisher, the lead of its team, the orchestrator or the owner may do it, and the target must be a member of an active team on this machine. Afterwards the new publisher publishes with `--app <appId> --dir <its directory>`, comments and changes go to it, and the old team can no longer publish, roll back or use the app's data. Both agents get a short note.
|
|
136
140
|
- Thumbnails: the owner's portal list shows a small screenshot of each app. Publishing (and rollback) takes it automatically in the background when this machine has Chrome or Chromium; `bash execute.sh --app <appId> --refresh-thumbnail` re-takes it now (answers `captured:false` with a reason such as `no_browser` when it cannot).
|
|
137
141
|
- `--private` is always allowed and takes effect at once. Use it if anything
|
|
@@ -29,6 +29,7 @@ Usage:
|
|
|
29
29
|
bash execute.sh --app <appId> --rollback <version> # make an earlier version current
|
|
30
30
|
bash execute.sh --app <appId> --versions # list versions
|
|
31
31
|
bash execute.sh --app <appId> --transfer-to <session> # hand the app to another agent / team
|
|
32
|
+
bash execute.sh --app <appId> --owner 'channel:#daily-brief' # who gets the owner's comments (also with a publish)
|
|
32
33
|
bash execute.sh --list # apps published from this machine
|
|
33
34
|
bash execute.sh --app <appId> --share [--ttl-days 7] # fresh one-tap link card to the owner (no publish)
|
|
34
35
|
bash execute.sh --app <appId> --links # open-links of the app (no secrets)
|
|
@@ -63,6 +64,8 @@ Options:
|
|
|
63
64
|
--rollback Version number to make current (needs --app)
|
|
64
65
|
--versions List versions (needs --app)
|
|
65
66
|
--transfer-to Hand the app to another agent (its session name; it must be on a team on this machine).
|
|
67
|
+
--owner Who owns the app's comments: channel:#<name>, team:<name>, agent:<name> or default (the publishing agent).
|
|
68
|
+
With a publish it is set after the publish; alone it needs --app. Only the app's owner agents may change it.
|
|
66
69
|
Allowed for the app's publisher, the lead of its team, the orchestrator or the owner.
|
|
67
70
|
The new publisher publishes with --app <appId> --dir <its directory>; the old team loses access.
|
|
68
71
|
--list List apps published from this machine
|
|
@@ -84,7 +87,7 @@ call() {
|
|
|
84
87
|
}
|
|
85
88
|
|
|
86
89
|
DIR=""; FILE=""; NAME=""; APP=""; ENTRY=""; NOTE=""; NOTIFY=false; ROLLBACK=""; TRANSFER_TO=""; VERSIONS=false; LIST=false
|
|
87
|
-
SHARE=false; TTL_DAYS=""; LINKS=false; REVOKE_LINK=""; REVOKE_LINKS=false
|
|
90
|
+
SHARE=false; TTL_DAYS=""; LINKS=false; REVOKE_LINK=""; REVOKE_LINKS=false; OWNER_SPEC=""
|
|
88
91
|
PUBLIC=false; PUBLIC_READ=""; PUBLIC_SUBMIT=""; PUBLIC_NOTE=""; CANCEL_PUBLIC=false; PRIVATE=false; REFRESH_THUMB=false
|
|
89
92
|
while [[ $# -gt 0 ]]; do
|
|
90
93
|
case "$1" in
|
|
@@ -110,6 +113,7 @@ while [[ $# -gt 0 ]]; do
|
|
|
110
113
|
--refresh-thumbnail) REFRESH_THUMB=true; shift ;;
|
|
111
114
|
--versions) VERSIONS=true; shift ;;
|
|
112
115
|
--transfer-to) [ $# -ge 2 ] || error_exit "--transfer-to requires a session name"; TRANSFER_TO="$2"; shift 2 ;;
|
|
116
|
+
--owner) [ $# -ge 2 ] || error_exit "--owner requires channel:#<name>, team:<name>, agent:<name> or default"; OWNER_SPEC="$2"; shift 2 ;;
|
|
113
117
|
--list) LIST=true; shift ;;
|
|
114
118
|
--help|-h) print_usage; exit 0 ;;
|
|
115
119
|
*) error_exit "Unknown option: $1" ;;
|
|
@@ -201,6 +205,15 @@ if [ -z "$DIR" ] && [ -z "$FILE" ]; then
|
|
|
201
205
|
fi
|
|
202
206
|
fi
|
|
203
207
|
|
|
208
|
+
# The app's owner (crewly-services apps/SPEC.md §15): who its comments go to.
|
|
209
|
+
OWNER_JQ='(.data.owner | if . == null then null else {kind, name, default: (.explicit | not)} end)'
|
|
210
|
+
if [ -n "$OWNER_SPEC" ] && [ -z "$DIR" ] && [ -z "$FILE" ]; then
|
|
211
|
+
[ -n "$APP" ] || error_exit "--app is required with --owner (or publish with --dir / --html)"
|
|
212
|
+
RESPONSE=$(call PUT "/apps/${APP}/owner" "$(jq -cn --arg o "$OWNER_SPEC" '{owner: $o}')") || { printf '%s\n' "$RESPONSE"; exit 1; }
|
|
213
|
+
printf '%s' "$RESPONSE" | jq -c '{success: true, appId: .data.appId, owner: '"$OWNER_JQ"'}'
|
|
214
|
+
exit 0
|
|
215
|
+
fi
|
|
216
|
+
|
|
204
217
|
if [ -n "$TRANSFER_TO" ]; then
|
|
205
218
|
[ -n "$APP" ] || error_exit "--app is required with --transfer-to"
|
|
206
219
|
BODY=$(jq -cn --arg s "$TRANSFER_TO" '{toSession: $s}')
|
|
@@ -311,9 +324,19 @@ OUT=$(curl -s -w '\n%{http_code}' -X POST -H "Content-Type: application/json" ${
|
|
|
311
324
|
CODE=$(printf '%s\n' "$OUT" | tail -n 1)
|
|
312
325
|
RESP=$(printf '%s\n' "$OUT" | sed '$d')
|
|
313
326
|
if [ "$CODE" -ge 200 ] 2>/dev/null && [ "$CODE" -lt 300 ] 2>/dev/null; then
|
|
314
|
-
|
|
327
|
+
OWNER_OUT='{}'
|
|
328
|
+
if [ -n "$OWNER_SPEC" ]; then
|
|
329
|
+
NEW_APP=$(printf '%s' "$RESP" | jq -r '.data.appId // empty')
|
|
330
|
+
if OWNER_RESP=$(call PUT "/apps/${NEW_APP}/owner" "$(jq -cn --arg o "$OWNER_SPEC" '{owner: $o}')"); then
|
|
331
|
+
OWNER_OUT=$(printf '%s' "$OWNER_RESP" | jq -c '{owner: '"$OWNER_JQ"'}')
|
|
332
|
+
else
|
|
333
|
+
# The publish went through; only the owner change failed.
|
|
334
|
+
OWNER_OUT=$(printf '%s' "$OWNER_RESP" | jq -c '{ownerError: (.message // .reason // "failed")}' 2>/dev/null || echo '{"ownerError":"failed"}')
|
|
335
|
+
fi
|
|
336
|
+
fi
|
|
337
|
+
printf '%s' "$RESP" | jq -c --arg msg "$PUBLIC_MSG" --argjson own "$OWNER_OUT" '.data | {success: true, appId, name, url: ("https://apps.crewlyai.com/" + .appId), version, created, notified} + '"$CARD_JQ"'
|
|
315
338
|
+ (if .publicRequested == true then {publicRequested: true, message: $msg} elif .publicRequested == false then {publicRequested: false, publicError} else {} end)
|
|
316
|
-
+ (if .publicPaused == true then {publicPaused: true, publicPausedMessage} else {} end)'
|
|
339
|
+
+ (if .publicPaused == true then {publicPaused: true, publicPausedMessage} else {} end) + $own'
|
|
317
340
|
else
|
|
318
341
|
printf '%s' "$RESP" | jq -c --arg code "$CODE" '{success: false, status: ($code|tonumber), reason: (.error // "unknown"), message: (.message // ""), hint: (.hint // "")}' 2>/dev/null \
|
|
319
342
|
|| jq -cn --arg code "$CODE" '{success: false, status: ($code|tonumber? // 0), reason: "http_error"}'
|
|
@@ -63,6 +63,10 @@ class H(BaseHTTPRequestHandler):
|
|
|
63
63
|
return send(200, {'success': True, 'data': {'appId': 'bcdfghjkmn', 'captured': False, 'reason': 'no_browser', 'message': 'No Chrome'}})
|
|
64
64
|
if self.path == '/api/apps/28au74d9cj/make-private':
|
|
65
65
|
return send(200, {'success': True, 'data': {'appId': '28au74d9cj', 'visibility': 'private'}})
|
|
66
|
+
if self.path.endswith('/owner') and method == 'PUT':
|
|
67
|
+
if data.get('owner') == 'channel:#nope':
|
|
68
|
+
return send(400, {'success': False, 'error': 'validation', 'message': 'No channel "#nope" on this machine.'})
|
|
69
|
+
return send(200, {'success': True, 'data': {'appId': '28au74d9cj', 'owner': {'kind': 'channel', 'explicit': True, 'name': '#daily-brief'}, 'previous': None}})
|
|
66
70
|
if self.path == '/api/apps/28au74d9cj/transfer':
|
|
67
71
|
return send(200, {'success': True, 'data': {'appId': '28au74d9cj', 'name': 'G', 'publisher': data['toSession'], 'previous': 'dev-ella', 'changed': True, 'notified': [data['toSession']]}})
|
|
68
72
|
if self.path.endswith('/rollback'):
|
|
@@ -79,6 +83,9 @@ class H(BaseHTTPRequestHandler):
|
|
|
79
83
|
def do_POST(self):
|
|
80
84
|
n = int(self.headers.get('content-length', '0')); body = self.rfile.read(n).decode() if n else ''
|
|
81
85
|
self._reply('POST', json.loads(body or '{}'))
|
|
86
|
+
def do_PUT(self):
|
|
87
|
+
n = int(self.headers.get('content-length', '0')); body = self.rfile.read(n).decode() if n else ''
|
|
88
|
+
self._reply('PUT', json.loads(body or '{}'))
|
|
82
89
|
def log_message(self, *a, **k): pass
|
|
83
90
|
HTTPServer(('127.0.0.1', PORT), H).serve_forever()
|
|
84
91
|
PY
|
|
@@ -220,5 +227,16 @@ check "refresh-thumbnail without a browser" "$OUT" '{"success":false,"appId":"bc
|
|
|
220
227
|
OUT=$(run_err --dir "$APPDIR" --refresh-thumbnail || true)
|
|
221
228
|
check "--refresh-thumbnail is not a publish option" "$(printf '%s' "$OUT" | grep -c 'act on an app')" "1"
|
|
222
229
|
|
|
230
|
+
# --owner (crewly-services apps/SPEC.md §15)
|
|
231
|
+
OUT=$(run --app 28au74d9cj --owner 'channel:#daily-brief')
|
|
232
|
+
check "owner alone: output" "$OUT" '{"success":true,"appId":"28au74d9cj","owner":{"kind":"channel","name":"#daily-brief","default":false}}'
|
|
233
|
+
check "owner alone: request" "$(jq -c '{method, path, body}' "$STUB_LOG")" '{"method":"PUT","path":"/api/apps/28au74d9cj/owner","body":{"owner":"channel:#daily-brief"}}'
|
|
234
|
+
check "owner alone needs --app" "$(run_err --owner 'team:dev' | grep -c -- '--app is required with --owner')" "1"
|
|
235
|
+
OUT=$(run --dir "$APPDIR" --owner 'channel:#daily-brief')
|
|
236
|
+
check "publish + owner: set after the publish" "$(printf '%s' "$OUT" | jq -c '{success, owner}')" '{"success":true,"owner":{"kind":"channel","name":"#daily-brief","default":false}}'
|
|
237
|
+
check "publish + owner: last request is the owner change" "$(jq -r '.method + " " + .path' "$STUB_LOG")" "PUT /api/apps/28au74d9cj/owner"
|
|
238
|
+
OUT=$(run --dir "$APPDIR" --owner 'channel:#nope')
|
|
239
|
+
check "publish + bad owner: publish still succeeds, ownerError says why" "$(printf '%s' "$OUT" | jq -c '{success, ownerError}')" '{"success":true,"ownerError":"No channel \"#nope\" on this machine."}'
|
|
240
|
+
|
|
223
241
|
echo "publish-app: $PASS passed, $FAIL failed"
|
|
224
242
|
[ "$FAIL" -eq 0 ]
|