@xdxer/dingtalk-agent 0.1.4-beta.9 → 0.1.5-beta.1
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 +229 -0
- package/README.en.md +89 -324
- package/README.md +88 -674
- package/dist/bin/dingtalk-agent.js +207 -21
- package/dist/bin/dingtalk-agent.js.map +1 -1
- package/dist/src/agent-audit.js +1014 -89
- package/dist/src/agent-audit.js.map +1 -1
- package/dist/src/agent-enhance.js +41 -8
- package/dist/src/agent-enhance.js.map +1 -1
- package/dist/src/agent-platform.js +299 -0
- package/dist/src/agent-platform.js.map +1 -0
- package/dist/src/config.js +1 -7
- package/dist/src/config.js.map +1 -1
- package/dist/src/development-workspace.js +31 -5
- package/dist/src/development-workspace.js.map +1 -1
- package/dist/src/doctor.js +74 -15
- package/dist/src/doctor.js.map +1 -1
- package/dist/src/host-detect.js +146 -0
- package/dist/src/host-detect.js.map +1 -0
- package/dist/src/instruction-path.js +270 -0
- package/dist/src/instruction-path.js.map +1 -0
- package/dist/src/multica-deploy.js +78 -20
- package/dist/src/multica-deploy.js.map +1 -1
- package/dist/src/multica-provider.js +1 -1
- package/dist/src/multica-provider.js.map +1 -1
- package/dist/src/opencode-evals.js +710 -225
- package/dist/src/opencode-evals.js.map +1 -1
- package/dist/src/opencode-isolation.js +124 -0
- package/dist/src/opencode-isolation.js.map +1 -0
- package/dist/src/opencode-provider.js +1 -0
- package/dist/src/opencode-provider.js.map +1 -1
- package/dist/src/opencode-workspace.js +21 -10
- package/dist/src/opencode-workspace.js.map +1 -1
- package/dist/src/remote-state-evals.js +2 -1
- package/dist/src/remote-state-evals.js.map +1 -1
- package/dist/src/robot-evals.js +2 -1
- package/dist/src/robot-evals.js.map +1 -1
- package/dist/src/setup.js +6 -5
- package/dist/src/setup.js.map +1 -1
- package/dist/src/skill-manager.js +141 -12
- package/dist/src/skill-manager.js.map +1 -1
- package/dist/src/skills.js +2 -1
- package/dist/src/skills.js.map +1 -1
- package/dist/src/types.js.map +1 -1
- package/dist/src/upgrade.js +23 -27
- package/dist/src/upgrade.js.map +1 -1
- package/dist/src/version.js +73 -0
- package/dist/src/version.js.map +1 -0
- package/docs/ARCHITECTURE.md +232 -0
- package/docs/INSTALLATION.md +3 -3
- package/docs/PRIOR-ART.md +122 -0
- package/docs/SELF-TEST.md +182 -0
- package/docs/assets/agent-delivery-lifecycle.svg +103 -0
- package/docs/schemas/agent-platform.schema.json +13 -0
- package/docs/schemas/project.schema.json +3 -0
- package/docs/schemas/release-readiness.schema.json +2 -1
- package/evals/README.md +17 -0
- package/examples/agents/README.md +43 -0
- package/examples/agents/fde-coach/AGENTS.md +26 -17
- package/examples/agents/release-manager/AGENTS.md +26 -17
- package/lab/README.md +3 -3
- package/lab/agent-eval/catalog.json +5 -5
- package/lab/agent-eval/classic-failures.json +9 -9
- package/lab/agent-eval/completion-gate-regression.json +6 -6
- package/lab/agent-eval/remote-state-workspace/opencode.json +1 -1
- package/lab/agent-eval/workspace/AGENTS.md +1 -1
- package/lab/project-workspace/fake-multica-provider.mjs +17 -6
- package/lab/project-workspace/opencode-provider-suite.json +2 -2
- package/lab/robot-eval/suite.json +1 -1
- package/lab/robot-eval/workspace/AGENTS.md +1 -1
- package/lab/schemas/agent-eval-catalog.schema.json +1 -1
- package/package.json +18 -13
- package/skills/README.md +23 -0
- package/skills/core/dingtalk-agent-compose/SKILL.md +151 -0
- package/skills/core/dingtalk-agent-compose/assets/AGENTS.template.md +35 -0
- package/skills/core/dingtalk-agent-compose/assets/role-skill.template.md +32 -0
- package/skills/core/dingtalk-agent-compose/evals/evals.json +129 -0
- package/skills/{dingtalk-agent-compose → core/dingtalk-agent-compose}/references/agent-definition-contract.md +3 -3
- package/skills/core/dingtalk-agent-compose/references/host-loading-contract.md +58 -0
- package/skills/core/dingtalk-agent-compose/references/hosts/claude-code.md +48 -0
- package/skills/core/dingtalk-agent-compose/references/hosts/opencode.md +77 -0
- package/skills/{dingtalk-agent-eval → core/dingtalk-agent-eval}/SKILL.md +57 -8
- package/skills/{dingtalk-agent-eval → core/dingtalk-agent-eval}/assets/eval-catalog.template.json +1 -1
- package/skills/{dingtalk-agent-eval → core/dingtalk-agent-eval}/evals/evals.json +22 -0
- package/skills/{dingtalk-agent-eval → core/dingtalk-agent-eval}/references/eval-topology.md +14 -0
- package/skills/{dingtalk-agent-eval → core/dingtalk-agent-eval}/references/evidence-contract.md +21 -0
- package/skills/core/dingtalk-agent-eval/references/failure-to-case.md +35 -0
- package/skills/core/dingtalk-agent-eval/references/interactive-debug-channels.md +93 -0
- package/skills/core/dingtalk-agent-eval/references/local-connector-smoke.md +75 -0
- package/skills/core/dingtalk-basic-behavior/SKILL.md +87 -0
- package/skills/{dingtalk-basic-behavior → core/dingtalk-basic-behavior}/references/memory-and-evolution.md +12 -0
- package/skills/core/dingtalk-basic-behavior/references/risk-authority-and-privacy.md +62 -0
- package/skills/{dingtalk-basic-behavior → core/dingtalk-basic-behavior}/references/task-lifecycle.md +15 -3
- package/skills/core/dingtalk-basic-behavior/references/truth-and-recovery.md +65 -0
- package/skills/platforms/deap/PLATFORM.md +3 -0
- package/skills/platforms/multica-dingtalk/PLATFORM.md +40 -0
- package/skills/{dingtalk-agent-boot-multica → platforms/multica-dingtalk/dingtalk-agent-boot-multica}/SKILL.md +4 -4
- package/skills/{dingtalk-agent-deploy → platforms/multica-dingtalk/dingtalk-agent-deploy-multica}/SKILL.md +1 -1
- package/skills/platforms/multica-dingtalk/multica-external/SKILL.md +282 -0
- package/skills/platforms/multica-dingtalk/multica-external/scripts/bootstrap.sh +78 -0
- package/skills/platforms/multica-dingtalk/multica-external/scripts/multica_ext.py +1180 -0
- package/docs/SECOND-AGENT-ACCEPTANCE.md +0 -62
- package/docs/architecture/agent-memory-topology.png +0 -0
- package/docs/architecture/agent-memory-topology.svg +0 -132
- package/docs/architecture/dingtalk-agent-blueprint.png +0 -0
- package/docs/architecture/durable-async-agent-runtime.png +0 -0
- package/docs/architecture/general-agent-kernel-topology.png +0 -0
- package/docs/architecture/provider-bound-development-workspace.png +0 -0
- package/docs/architecture/task-completion-gate.png +0 -0
- package/skills/dingtalk-agent-compose/SKILL.md +0 -110
- package/skills/dingtalk-agent-compose/assets/AGENTS.template.md +0 -26
- package/skills/dingtalk-agent-compose/assets/role-skill.template.md +0 -24
- package/skills/dingtalk-agent-compose/evals/evals.json +0 -94
- package/skills/dingtalk-agent-compose/references/opencode-host-contract.md +0 -65
- package/skills/dingtalk-basic-behavior/SKILL.md +0 -146
- /package/skills/{dingtalk-agent-compose → core/dingtalk-agent-compose}/assets/agent.bindings.dingtalk-doc.template.json +0 -0
- /package/skills/{dingtalk-agent-compose → core/dingtalk-agent-compose}/assets/agent.bindings.local.template.json +0 -0
- /package/skills/{dingtalk-agent-compose/assets → core/dingtalk-agent-compose/assets/hosts/opencode}/opencode.template.json +0 -0
- /package/skills/{dingtalk-agent-compose → core/dingtalk-agent-compose}/references/storage-routing.md +0 -0
- /package/skills/{dingtalk-agent-eval → core/dingtalk-agent-eval}/references/scenario-taxonomy.md +0 -0
- /package/skills/{dingtalk-agent-eval → core/dingtalk-agent-eval}/references/storage-modes.md +0 -0
- /package/skills/{dingtalk-basic-behavior → core/dingtalk-basic-behavior}/assets/memory-candidate-proposal.json +0 -0
- /package/skills/{dingtalk-basic-behavior → core/dingtalk-basic-behavior}/assets/task-checkpoint.json +0 -0
- /package/skills/{dingtalk-basic-behavior → core/dingtalk-basic-behavior}/references/action-contract.md +0 -0
- /package/skills/{dingtalk-basic-behavior → core/dingtalk-basic-behavior}/references/event-to-behavior.md +0 -0
- /package/skills/{dingtalk-basic-behavior → core/dingtalk-basic-behavior}/references/perception-and-gates.md +0 -0
- /package/skills/{dingtalk-basic-behavior → core/dingtalk-basic-behavior}/references/runtime-modes.md +0 -0
- /package/skills/{dingtalk-agent-deploy → platforms/multica-dingtalk/dingtalk-agent-deploy-multica}/references/multica-deployment-contract.md +0 -0
- /package/skills/{dingtalk-agent-deploy → platforms/multica-dingtalk/dingtalk-agent-deploy-multica}/references/promotion-observation-contract.md +0 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: multica-external
|
|
3
|
+
description: External Python wrapper defining the full developer chain for delivering an agent on the Multica (DingTalk-FDE fork) platform — workspace bootstrap, runtime/agent provisioning, skill assembly (push/pull), DingTalk robot & account binding, execution-trace observability, cron scheduling (autopilot), and health checks. Use when provisioning, operating, debugging, or observing a Multica-managed agent from the command line with only an endpoint and a token.
|
|
4
|
+
metadata:
|
|
5
|
+
version: "0.1.0"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Multica FDE External Skill
|
|
9
|
+
|
|
10
|
+
This skill is the **Multica adapter** of the Managed Agent Platform developer
|
|
11
|
+
chain: everything a developer needs to take an agent from nothing to a
|
|
12
|
+
customer-facing DingTalk bot, then observe, schedule, and health-check it.
|
|
13
|
+
Multica may be only one of several managed platforms; this skill defines
|
|
14
|
+
exactly which capabilities the Multica backend provides and wraps each one as
|
|
15
|
+
a script command. An external orchestrator can fetch this skill (see
|
|
16
|
+
*Skill logistics*) and execute its scripts.
|
|
17
|
+
|
|
18
|
+
Deliberately **decoupled from the Go CLI**: nothing patches `multica`;
|
|
19
|
+
everything is plain HTTPS against the server. Only two inputs are needed —
|
|
20
|
+
**server URL** and **token** — both reusable from the CLI's stored config.
|
|
21
|
+
|
|
22
|
+
- Script: `scripts/multica_ext.py` — single file, stdlib only, Python >= 3.9.
|
|
23
|
+
- Proxy-immune: the script ignores `HTTP(S)_PROXY` env vars by design (unlike
|
|
24
|
+
curl or the Go CLI), so an exported shell proxy cannot break pre/intranet
|
|
25
|
+
endpoints.
|
|
26
|
+
- All commands print JSON to stdout (status notes on stderr) — pipe to `jq`.
|
|
27
|
+
- Global flags may appear before or after the command.
|
|
28
|
+
|
|
29
|
+
## Credentials
|
|
30
|
+
|
|
31
|
+
Resolution order (first hit wins):
|
|
32
|
+
|
|
33
|
+
1. Flags: `--server-url`, `--token`, `--workspace <slug|uuid>`
|
|
34
|
+
2. Env: `MULTICA_SERVER_URL`, `MULTICA_TOKEN`, `MULTICA_WORKSPACE_ID`
|
|
35
|
+
3. `--profile <name>` → `~/.multica/profiles/<name>/config.json`
|
|
36
|
+
4. `~/.multica/config.json` (keys: `server_url`, `workspace_id`, `token`)
|
|
37
|
+
|
|
38
|
+
The token is a `mul_` personal access token or login JWT; mint PATs with
|
|
39
|
+
`POST /api/tokens`.
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
PY=".agents/skills/multica-external/scripts/multica_ext.py"
|
|
43
|
+
python3 $PY --help
|
|
44
|
+
python3 $PY whoami --profile pre-fde
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## The developer chain
|
|
48
|
+
|
|
49
|
+
### 1 — Provision: workspace, runtime, agent
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
python3 $PY workspace-init --profile pre-fde --name "FDE 团队" --slug fde-team \
|
|
53
|
+
--repo https://code.example.com/org/repo.git --complete-onboarding
|
|
54
|
+
python3 $PY runtime-create-fc --workspace fde-team --template-id <id> # cloud FC-E2B runtime
|
|
55
|
+
python3 $PY agent-create --workspace fde-team --name "FDE 教练" \
|
|
56
|
+
--instructions "..." --model claude-sonnet-5 # sole runtime auto-picked
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`workspace-init` is idempotent (409 → reuse when you are a member); repos
|
|
60
|
+
merge, never clobber; repo registration needs owner/admin. Slugs:
|
|
61
|
+
`^[a-z0-9]+(-[a-z0-9]+)*$`, reserved list enforced. An agent needs a runtime:
|
|
62
|
+
FC-E2B cloud (`runtime-create-fc`, requires server-side FC-E2B enablement) or
|
|
63
|
+
a local daemon. `runtime-templates` lists templates; when `--template-id` is
|
|
64
|
+
omitted and exactly one exists it is auto-picked.
|
|
65
|
+
|
|
66
|
+
### 2 — Define & assemble: where the agent comes from, what skills it has
|
|
67
|
+
|
|
68
|
+
- `agent-source --agent <uuid>` — which git repo backs the agent (the
|
|
69
|
+
per-agent `agent_sources` link: `github` or `managed_git`; `null` = not
|
|
70
|
+
git-backed). `agent-source-sync` re-syncs github-sourced agents
|
|
71
|
+
(`managed_git` refuses with 409 by design — updated by the platform).
|
|
72
|
+
- Skills: `skill-list`, `skill-get`, `skill-push --dir <path>`
|
|
73
|
+
(local directory → workspace, `--on-conflict overwrite` replaces content
|
|
74
|
+
AND files wholesale), `skill-pull --skill <id-or-name> --dest <dir>`
|
|
75
|
+
(workspace → local directory). Assign skills to an agent with the Go CLI
|
|
76
|
+
(`multica agent skills add`) or `PUT /api/agents/{id}/skills`.
|
|
77
|
+
- Gap to know: there is no API to bind an *arbitrary* git URL to an agent;
|
|
78
|
+
per-agent git links are created only by the GitHub publish flow or the
|
|
79
|
+
managed FDE provision flow. Workspace-level repos (`workspace-repos`) and
|
|
80
|
+
the two flows above are the supported paths.
|
|
81
|
+
|
|
82
|
+
### 3 — Deliver: bind the agent to a customer-facing DingTalk bot
|
|
83
|
+
|
|
84
|
+
The binding is a **channel installation** (one robot app per agent), not a
|
|
85
|
+
Skill object. AppKey/AppSecret are `client_id`/`client_secret` on the wire;
|
|
86
|
+
the secret is encrypted at rest (`MULTICA_DINGTALK_SECRET_KEY`) and never
|
|
87
|
+
returned. Inbound messages use DingTalk Stream Mode — no public webhook
|
|
88
|
+
registration needed.
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
# Self-service: inject an existing robot app's credentials (owner/admin)
|
|
92
|
+
python3 $PY dingtalk-bind --agent <uuid> --client-id <AppKey> --client-secret-stdin < secret.txt
|
|
93
|
+
# Or scan-to-create: DingTalk mints the app, server captures credentials
|
|
94
|
+
python3 $PY dingtalk-begin --agent <uuid> --wait
|
|
95
|
+
python3 $PY dingtalk-list ; python3 $PY dingtalk-revoke --installation <uuid>
|
|
96
|
+
# Personal-account binding family (dingtalk_account):
|
|
97
|
+
python3 $PY dingtalk-account-begin --agent <uuid> # prints QR URL
|
|
98
|
+
python3 $PY dingtalk-account-list ; python3 $PY dingtalk-account-unbind --installation <uuid>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Bind and unbind always come in pairs: robot `dingtalk-bind`/`dingtalk-begin`
|
|
102
|
+
↔ `dingtalk-revoke`; account `dingtalk-account-begin` ↔
|
|
103
|
+
`dingtalk-account-unbind`. `dingtalk-account-begin` returns the binding link
|
|
104
|
+
(`qr_code_url`) directly — hand it to the counterpart to scan; check the
|
|
105
|
+
result with `dingtalk-account-list` (`dws_identity` carries the bound
|
|
106
|
+
account's org/display name).
|
|
107
|
+
|
|
108
|
+
Server rules: one robot per agent; re-binding the same `client_id` **moves**
|
|
109
|
+
the installation; cross-workspace `client_id` reuse → 409. Error map for
|
|
110
|
+
`dingtalk-bind`: **502** "dingtalk credentials check failed" = wrong
|
|
111
|
+
AppKey/AppSecret; **409** = ownership/agent conflict; **404** = agent not in
|
|
112
|
+
this workspace; `configured: false` in `dingtalk-list` = server lacks the
|
|
113
|
+
secret key. Deletes are audit-kept (`status: revoked`).
|
|
114
|
+
|
|
115
|
+
### 4 — Observe: the agent's working and reasoning process
|
|
116
|
+
|
|
117
|
+
The full step-by-step trace (reasoning text, every tool call with input JSON,
|
|
118
|
+
every tool result, errors) is **persisted forever** in `task_messages` and
|
|
119
|
+
retrievable by REST — no backend changes needed:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
python3 $PY agent-tasks --agent <uuid> # run history (status, error, failure_reason, kind)
|
|
123
|
+
python3 $PY task-trace --task <uuid> --text # full reasoning/tool trace, human-readable
|
|
124
|
+
python3 $PY task-trace --task <uuid> --follow --text # live: polls until the task ends
|
|
125
|
+
python3 $PY issue-tasks --issue FDE-1 # all runs for an issue (uuid or identifier)
|
|
126
|
+
python3 $PY issue-timeline --issue FDE-1 # comments + activity log merged (status changes, task_completed...)
|
|
127
|
+
python3 $PY issue-usage --issue FDE-1 # aggregated token usage
|
|
128
|
+
python3 $PY task-snapshot # workspace-wide: active tasks + each agent's latest outcome
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Chat surfaces are REST-pollable too (`/api/chat/sessions/...` — session
|
|
132
|
+
messages, `pending-task`); realtime push is WebSocket `/ws` only (no SSE),
|
|
133
|
+
but a poll-only client fully reconstructs any past run from `task-trace`.
|
|
134
|
+
|
|
135
|
+
### 5 — Schedule: cron automations (autopilot)
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
python3 $PY autopilot-create --title "每日巡检" --agent <uuid> \
|
|
139
|
+
--description "<the prompt for each run>" --cron "0 9 * * 1-5" --timezone Asia/Shanghai
|
|
140
|
+
python3 $PY autopilot-trigger --autopilot <uuid> # run once, now
|
|
141
|
+
python3 $PY autopilot-runs --autopilot <uuid> # history: status, linked issue/task
|
|
142
|
+
python3 $PY autopilot-set-status --autopilot <uuid> --status paused # pause / active
|
|
143
|
+
python3 $PY autopilot-list ; python3 $PY autopilot-get --autopilot <uuid>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Cron is standard 5-field (no seconds, no `@daily`), IANA timezone (default
|
|
147
|
+
UTC in the API; this script defaults `Asia/Shanghai`). `create_issue` mode
|
|
148
|
+
creates an issue per run (title template supports `{{date}}`) and enqueues an
|
|
149
|
+
agent task; `run_only` enqueues a task with no issue. Runs are idempotent per
|
|
150
|
+
(trigger, planned time); missed fires collapse to the latest, >5 min late is
|
|
151
|
+
skipped. Webhook triggers also exist (`POST /api/autopilots/{id}/triggers`
|
|
152
|
+
with `kind: webhook`).
|
|
153
|
+
|
|
154
|
+
### 6 — Check: doctor report
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
python3 $PY agent-check --agent <uuid>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Composite JSON report: agent status/archived, runtime health (online /
|
|
161
|
+
unstable = offline but seen <5 min ago / offline / missing), active tasks +
|
|
162
|
+
last outcome (with `failure_reason`), recent failure count, DingTalk robot +
|
|
163
|
+
account binding state, plus a `checks[]` verdict list; `healthy` = no `fail`
|
|
164
|
+
(warns don't fail the check).
|
|
165
|
+
|
|
166
|
+
### 7 — Basics: issues, comments, chat, members, tokens
|
|
167
|
+
|
|
168
|
+
Everyday Multica operations so an orchestrator needs nothing but this script:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
python3 $PY issue-create --title "..." --assignee-agent <uuid> # assigning an agent triggers a run
|
|
172
|
+
python3 $PY issue-get --issue FDE-1 ; python3 $PY issue-list --open-only --assignee <uuid>
|
|
173
|
+
python3 $PY issue-update --issue FDE-1 --status done
|
|
174
|
+
python3 $PY comment-add --issue FDE-1 --content "..." # "/note " prefix suppresses agent triggering
|
|
175
|
+
python3 $PY comment-list --issue FDE-1 --recent 10
|
|
176
|
+
python3 $PY chat-send --agent <uuid> --content "你好" --wait # direct conversation — the no-DingTalk e2e test path
|
|
177
|
+
python3 $PY member-list ; python3 $PY member-invite --email a@b.c --role member
|
|
178
|
+
python3 $PY token-create --name ci-bot --expires-days 90 # plaintext token shown once
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
`chat-send` creates a session when `--session` is omitted and with `--wait`
|
|
182
|
+
polls `pending-task` until the assistant reply lands — the fastest way to
|
|
183
|
+
smoke-test a freshly provisioned agent before binding any channel.
|
|
184
|
+
|
|
185
|
+
## Skill logistics: how skills move (the three planes)
|
|
186
|
+
|
|
187
|
+
- **Up (local → workspace):** `skill-push --dir` (this script);
|
|
188
|
+
`multica skill import --url|--file` (URL / `.skill`/`.zip` archive);
|
|
189
|
+
or the runtime *local-skills* API — the daemon scans the machine's
|
|
190
|
+
home-level skill dirs (`~/.claude/skills`, `~/.codex/skills`, the
|
|
191
|
+
cross-tool universal root `~/.agents/skills`, Claude plugin roots, etc.;
|
|
192
|
+
a "skill" = a directory with `SKILL.md`) and the workspace can list and
|
|
193
|
+
import them (`POST /api/runtimes/{id}/local-skills[/import]`, owner-only
|
|
194
|
+
for import).
|
|
195
|
+
- **Store:** workspace DB (`content` = SKILL.md, `files[]` = supporting
|
|
196
|
+
files; `SKILL.md` is a reserved file path).
|
|
197
|
+
- **Down (workspace → local):** automatic — the daemon materializes assigned
|
|
198
|
+
skills into each task's exec env (per-provider dirs, e.g.
|
|
199
|
+
`.claude/skills`, ephemeral, task-scoped). Manual/persistent — the Go CLI
|
|
200
|
+
has **no** download command; `skill-pull` (this script) is the downlink:
|
|
201
|
+
it writes `SKILL.md` + files into a local directory, byte-identical to
|
|
202
|
+
what was pushed. An external orchestrator can therefore
|
|
203
|
+
`skill-pull --skill multica-external --dest <dir>` and execute the
|
|
204
|
+
scripts it receives.
|
|
205
|
+
|
|
206
|
+
## Command → API map
|
|
207
|
+
|
|
208
|
+
| Command | API |
|
|
209
|
+
| --- | --- |
|
|
210
|
+
| `whoami` | `GET /api/me` |
|
|
211
|
+
| `workspace-list` / `workspace-create` / `workspace-init` | `GET`/`POST /api/workspaces` (+ `PUT /api/workspaces/{id}`, `POST /api/me/onboarding/complete`) |
|
|
212
|
+
| `workspace-repos` | `GET`/`PUT /api/workspaces/{id}` (`repos` JSONB) |
|
|
213
|
+
| `runtime-list` / `runtime-templates` / `runtime-create-fc` | `GET /api/runtimes`, `GET`/`POST /api/runtimes/fc-e2b(/templates)` |
|
|
214
|
+
| `agent-list` / `agent-get` / `agent-create` | `GET`/`POST /api/agents` |
|
|
215
|
+
| `agent-source` / `agent-source-sync` | `GET /api/agents/{id}/source`, `POST .../source/sync` |
|
|
216
|
+
| `agent-tasks` | `GET /api/agents/{id}/tasks` |
|
|
217
|
+
| `task-trace` | `GET /api/tasks/{taskId}/messages?since=` (+ `GET /api/agent-task-snapshot` for follow-stop) |
|
|
218
|
+
| `issue-tasks` / `issue-timeline` / `issue-usage` | `GET /api/issues/{id}/task-runs` / `/timeline` / `/usage` |
|
|
219
|
+
| `task-snapshot` | `GET /api/agent-task-snapshot` |
|
|
220
|
+
| `autopilot-*` | `GET`/`POST`/`PATCH /api/autopilots`, `POST .../trigger`, `GET .../runs`, `POST .../triggers` |
|
|
221
|
+
| `agent-check` | composite of agents/runtimes/snapshot/tasks/dingtalk endpoints |
|
|
222
|
+
| `dingtalk-bind` / `dingtalk-begin` | `POST /api/workspaces/{id}/dingtalk/install/manual` / `install/begin` + status poll |
|
|
223
|
+
| `dingtalk-list` / `dingtalk-revoke` | `GET`/`DELETE .../dingtalk/installations` |
|
|
224
|
+
| `dingtalk-account-*` | `POST`/`GET`/`DELETE .../dingtalk/account-bindings` |
|
|
225
|
+
| `skill-list` / `skill-get` / `skill-pull` / `skill-push` | `GET /api/skills(/{id})`, `POST /api/skills`, `PUT /api/skills/{id}` |
|
|
226
|
+
| `issue-create` / `issue-get` / `issue-list` / `issue-update` | `POST`/`GET`/`PUT /api/issues(/{id})` |
|
|
227
|
+
| `comment-add` / `comment-list` | `POST`/`GET /api/issues/{id}/comments` |
|
|
228
|
+
| `chat-send` | `POST /api/chat/sessions(/{id}/messages)` + `GET .../pending-task` + `GET .../messages` |
|
|
229
|
+
| `member-list` / `member-invite` | `GET`/`POST /api/workspaces/{id}/members` |
|
|
230
|
+
| `token-create` | `POST /api/tokens` |
|
|
231
|
+
|
|
232
|
+
Auth: `Authorization: Bearer <token>`; workspace-scoped generic routes use
|
|
233
|
+
the `X-Workspace-ID` header, `/api/workspaces/{id}/...` routes take the UUID
|
|
234
|
+
in the path. No cookies, no CSRF.
|
|
235
|
+
|
|
236
|
+
## Distribution
|
|
237
|
+
|
|
238
|
+
This directory is self-contained (SKILL.md + scripts):
|
|
239
|
+
|
|
240
|
+
- Import into any Multica workspace: `multica skill import` from a URL or
|
|
241
|
+
`.skill`/`.zip` archive, or `skill-push` from this script. Files land in
|
|
242
|
+
agent envs without the exec bit — always invoke via
|
|
243
|
+
`python3 .../multica_ext.py` / `sh .../bootstrap.sh`, never `./x`.
|
|
244
|
+
- Or vendor into a dedicated git repo later; nothing depends on this
|
|
245
|
+
monorepo.
|
|
246
|
+
|
|
247
|
+
### Bootstrap: getting this skill onto a fresh machine
|
|
248
|
+
|
|
249
|
+
The workspace itself is the registry — no extra storage needed. `skill-pull`
|
|
250
|
+
lives inside the skill, so the first fetch uses `scripts/bootstrap.sh`
|
|
251
|
+
(copy it from a runbook, teammate, or this repo):
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
# Path A — authenticated multica CLI (no pull command needed: skill get
|
|
255
|
+
# outputs everything; bootstrap just writes it to disk):
|
|
256
|
+
sh bootstrap.sh --profile pre-fde --workspace-id <uuid> [--dest <dir>]
|
|
257
|
+
|
|
258
|
+
# Path B — no CLI at all, just endpoint + token (+ curl, python3):
|
|
259
|
+
MULTICA_SERVER_URL=... MULTICA_TOKEN=... MULTICA_WORKSPACE_ID=... \
|
|
260
|
+
sh bootstrap.sh --curl [--dest <dir>]
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Both paths produce a byte-identical copy of what `skill-push` uploaded, and
|
|
264
|
+
bootstrap.sh ships inside the bundle, so a pulled copy can bootstrap the
|
|
265
|
+
next machine. After bootstrap, stay current with
|
|
266
|
+
`python3 scripts/multica_ext.py skill-pull --skill multica-external --dest . --force`.
|
|
267
|
+
|
|
268
|
+
Equivalent one-liner where bootstrap.sh is unavailable:
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
multica skill get <skill-id> --output json | python3 -c '
|
|
272
|
+
import json,sys,pathlib
|
|
273
|
+
sk=json.load(sys.stdin); d=pathlib.Path(sk["name"]); d.mkdir(exist_ok=True)
|
|
274
|
+
(d/"SKILL.md").write_text(sk.get("content") or "")
|
|
275
|
+
for f in sk.get("files") or []:
|
|
276
|
+
p=d/f["path"]; p.parent.mkdir(parents=True,exist_ok=True); p.write_text(f.get("content") or "")'
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
OSS (or any object store) is an optional *anonymous* channel — useful only
|
|
280
|
+
when the target machine has no Multica credentials yet. It needs a publish
|
|
281
|
+
step on every update and bucket/ACL management, so prefer the
|
|
282
|
+
workspace-as-registry paths above when a token exists.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Bootstrap: fetch the multica-external skill from a Multica workspace
|
|
3
|
+
# onto this machine. Solves the chicken-and-egg problem (skill-pull lives
|
|
4
|
+
# inside the skill) using only tools already present: the multica CLI, or
|
|
5
|
+
# curl with endpoint+token. python3 is required either way (the skill's
|
|
6
|
+
# script needs it too).
|
|
7
|
+
#
|
|
8
|
+
# Path A (default) — authenticated multica CLI:
|
|
9
|
+
# sh bootstrap.sh [--profile <name>] [--workspace-id <uuid>] [--dest <dir>]
|
|
10
|
+
# Path B — no CLI, just endpoint + token:
|
|
11
|
+
# MULTICA_SERVER_URL=... MULTICA_TOKEN=... MULTICA_WORKSPACE_ID=... \
|
|
12
|
+
# sh bootstrap.sh --curl [--dest <dir>]
|
|
13
|
+
#
|
|
14
|
+
# curl runs with --noproxy '*' so an exported shell proxy cannot break
|
|
15
|
+
# intranet/pre endpoints (the Go CLI needs proxies unset by the caller).
|
|
16
|
+
set -eu
|
|
17
|
+
|
|
18
|
+
SKILL_NAME="multica-external"
|
|
19
|
+
DEST=""
|
|
20
|
+
PROFILE=""
|
|
21
|
+
WS_ID=""
|
|
22
|
+
MODE="cli"
|
|
23
|
+
|
|
24
|
+
while [ $# -gt 0 ]; do
|
|
25
|
+
case "$1" in
|
|
26
|
+
--profile) PROFILE="$2"; shift 2 ;;
|
|
27
|
+
--workspace-id) WS_ID="$2"; shift 2 ;;
|
|
28
|
+
--dest) DEST="$2"; shift 2 ;;
|
|
29
|
+
--name) SKILL_NAME="$2"; shift 2 ;;
|
|
30
|
+
--curl) MODE="curl"; shift ;;
|
|
31
|
+
*) echo "unknown argument: $1" >&2; exit 1 ;;
|
|
32
|
+
esac
|
|
33
|
+
done
|
|
34
|
+
|
|
35
|
+
RESOLVE='
|
|
36
|
+
import json, sys
|
|
37
|
+
name = sys.argv[1]
|
|
38
|
+
ids = [s.get("id") for s in json.load(sys.stdin) if isinstance(s, dict) and s.get("name") == name]
|
|
39
|
+
print(ids[0] if ids else "", end="")
|
|
40
|
+
'
|
|
41
|
+
|
|
42
|
+
UNPACK='
|
|
43
|
+
import json, sys, pathlib
|
|
44
|
+
sk = json.load(sys.stdin)
|
|
45
|
+
dest = sys.argv[1] if len(sys.argv) > 1 and sys.argv[1] else sk["name"]
|
|
46
|
+
d = pathlib.Path(dest)
|
|
47
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
48
|
+
(d / "SKILL.md").write_text(sk.get("content") or "", encoding="utf-8")
|
|
49
|
+
count = 1
|
|
50
|
+
for f in sk.get("files") or []:
|
|
51
|
+
rel = f.get("path") or ""
|
|
52
|
+
parts = pathlib.Path(rel).parts
|
|
53
|
+
if not parts or pathlib.Path(rel).is_absolute() or ".." in parts or rel == "SKILL.md":
|
|
54
|
+
continue
|
|
55
|
+
p = d / rel
|
|
56
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
57
|
+
p.write_text(f.get("content") or "", encoding="utf-8")
|
|
58
|
+
count += 1
|
|
59
|
+
print("pulled %d file(s) -> %s" % (count, d.resolve()))
|
|
60
|
+
'
|
|
61
|
+
|
|
62
|
+
if [ "$MODE" = "cli" ]; then
|
|
63
|
+
CLI="multica"
|
|
64
|
+
[ -n "$PROFILE" ] && CLI="$CLI --profile $PROFILE"
|
|
65
|
+
[ -n "$WS_ID" ] && CLI="$CLI --workspace-id $WS_ID"
|
|
66
|
+
SID=$($CLI skill list --output json | python3 -c "$RESOLVE" "$SKILL_NAME")
|
|
67
|
+
[ -n "$SID" ] || { echo "skill \"$SKILL_NAME\" not found in the workspace" >&2; exit 1; }
|
|
68
|
+
$CLI skill get "$SID" --output json | python3 -c "$UNPACK" "$DEST"
|
|
69
|
+
else
|
|
70
|
+
: "${MULTICA_SERVER_URL:?set MULTICA_SERVER_URL}"
|
|
71
|
+
: "${MULTICA_TOKEN:?set MULTICA_TOKEN}"
|
|
72
|
+
: "${MULTICA_WORKSPACE_ID:?set MULTICA_WORKSPACE_ID}"
|
|
73
|
+
AUTH="Authorization: Bearer $MULTICA_TOKEN"
|
|
74
|
+
WS="X-Workspace-ID: $MULTICA_WORKSPACE_ID"
|
|
75
|
+
SID=$(curl -fsS --noproxy '*' -H "$AUTH" -H "$WS" "$MULTICA_SERVER_URL/api/skills" | python3 -c "$RESOLVE" "$SKILL_NAME")
|
|
76
|
+
[ -n "$SID" ] || { echo "skill \"$SKILL_NAME\" not found in the workspace" >&2; exit 1; }
|
|
77
|
+
curl -fsS --noproxy '*' -H "$AUTH" -H "$WS" "$MULTICA_SERVER_URL/api/skills/$SID" | python3 -c "$UNPACK" "$DEST"
|
|
78
|
+
fi
|