@11agents/cli 0.1.40 → 0.1.42
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.
|
@@ -55,6 +55,82 @@ If device, logged-in account, target video, or measurement window is missing, as
|
|
|
55
55
|
11agents mobile add-metrics --record-id pub_123 --platform tiktok --metric views=0 --json --task-id TASK_123
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
+
6. When a project swarm token is available, map collected TikTok data into `swarm.telemetry.v1` instead of returning only a markdown summary:
|
|
59
|
+
- Use `platform: "tiktok_public"`.
|
|
60
|
+
- Account overview rows are `artifact_type: "account_period"`; create one artifact per account/period/date range and put `period`, `period_days`, `date_range`, account id, device id, and evidence paths in payload fields.
|
|
61
|
+
- Single-video rows are `artifact_type: "video"`; create one artifact per video/profile order/post id and put `profile_order`, `posted_time`, caption/title, account id, device id, and evidence paths in artifact or observation payload fields.
|
|
62
|
+
- Put numeric analytics in `observations[].metrics`.
|
|
63
|
+
- Include `dashboard_spec.metric_schema` and set recovered account/video metrics to `aggregation: "snapshot"` so Agent Performance does not sum values that already include a time window.
|
|
64
|
+
- Include `observation_table` widgets with explicit columns for account period rows and video rows so both account-level and single-video fields are visible.
|
|
65
|
+
|
|
66
|
+
## Swarm Dashboard Contract
|
|
67
|
+
|
|
68
|
+
Use explicit snapshot tables for TikTok metrics:
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"schema_version": "swarm.dashboard.v1",
|
|
73
|
+
"title": "TikTok Data Collection",
|
|
74
|
+
"metric_schema": {
|
|
75
|
+
"post_views": { "label": "Post views", "aggregation": "snapshot" },
|
|
76
|
+
"profile_views": { "label": "Profile views", "aggregation": "snapshot" },
|
|
77
|
+
"views": { "label": "Views", "aggregation": "snapshot" },
|
|
78
|
+
"likes": { "label": "Likes", "aggregation": "snapshot" },
|
|
79
|
+
"comments": { "label": "Comments", "aggregation": "snapshot" },
|
|
80
|
+
"shares": { "label": "Shares", "aggregation": "snapshot" },
|
|
81
|
+
"saves": { "label": "Saves", "aggregation": "snapshot" },
|
|
82
|
+
"avg_watch_seconds": { "label": "Avg watch", "aggregation": "snapshot", "unit": "seconds" },
|
|
83
|
+
"watched_full_rate": { "label": "Watched full", "aggregation": "snapshot", "format": "percent" },
|
|
84
|
+
"new_followers": { "label": "New followers", "aggregation": "snapshot" }
|
|
85
|
+
},
|
|
86
|
+
"widgets": [
|
|
87
|
+
{
|
|
88
|
+
"id": "account_periods",
|
|
89
|
+
"title": "Account Period Snapshots",
|
|
90
|
+
"type": "table",
|
|
91
|
+
"query": {
|
|
92
|
+
"kind": "observation_table",
|
|
93
|
+
"platform": "tiktok_public",
|
|
94
|
+
"artifact_type": "account_period",
|
|
95
|
+
"latest_per_artifact": true,
|
|
96
|
+
"columns": [
|
|
97
|
+
{ "key": "period", "label": "Period", "source": "payload" },
|
|
98
|
+
{ "key": "date_range", "label": "Date range", "source": "payload" },
|
|
99
|
+
{ "key": "post_views", "label": "Post views", "source": "metric" },
|
|
100
|
+
{ "key": "profile_views", "label": "Profile views", "source": "metric" },
|
|
101
|
+
{ "key": "likes", "label": "Likes", "source": "metric" },
|
|
102
|
+
{ "key": "comments", "label": "Comments", "source": "metric" },
|
|
103
|
+
{ "key": "shares", "label": "Shares", "source": "metric" }
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"id": "video_snapshots",
|
|
109
|
+
"title": "Video Snapshots",
|
|
110
|
+
"type": "table",
|
|
111
|
+
"query": {
|
|
112
|
+
"kind": "observation_table",
|
|
113
|
+
"platform": "tiktok_public",
|
|
114
|
+
"artifact_type": "video",
|
|
115
|
+
"latest_per_artifact": true,
|
|
116
|
+
"columns": [
|
|
117
|
+
{ "key": "profile_order", "label": "Profile order", "source": "payload" },
|
|
118
|
+
{ "key": "posted_time", "label": "Posted time", "source": "payload" },
|
|
119
|
+
{ "key": "views", "label": "Views", "source": "metric" },
|
|
120
|
+
{ "key": "likes", "label": "Likes", "source": "metric" },
|
|
121
|
+
{ "key": "comments", "label": "Comments", "source": "metric" },
|
|
122
|
+
{ "key": "shares", "label": "Shares", "source": "metric" },
|
|
123
|
+
{ "key": "saves", "label": "Saves", "source": "metric" },
|
|
124
|
+
{ "key": "avg_watch_seconds", "label": "Avg watch", "source": "metric" },
|
|
125
|
+
{ "key": "watched_full_rate", "label": "Watched full", "source": "metric" },
|
|
126
|
+
{ "key": "new_followers", "label": "New followers", "source": "metric" }
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
]
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
58
134
|
## Output
|
|
59
135
|
|
|
60
136
|
Return metric status, snapshot ids, account/video target, data freshness, screenshot paths when created, run log path, missing access, and `agent_memory_delta` with reusable creative or account learning.
|
|
@@ -41,9 +41,85 @@ If records, platform post id/permalink, or analytics access is missing, ask the
|
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
5. Treat X, Facebook, and unsupported platform collectors that return `missing_access` as a valid recovery status. Ask for the relevant API token, analytics export, or manual values, then append with `11agents mobile add-metrics`.
|
|
44
|
-
6.
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
6. For TikTok account/video recovery, map the data into `swarm.telemetry.v1` when the project swarm token is available:
|
|
45
|
+
- Use `platform: "tiktok_public"`.
|
|
46
|
+
- Use `artifact_type: "account_period"` for account overview period/date-range rows.
|
|
47
|
+
- Use `artifact_type: "video"` for single-video/profile-order rows.
|
|
48
|
+
- Put period/date range, posted time, profile order, account id, device id, and evidence paths in payload/artifact fields.
|
|
49
|
+
- Put numeric analytics in `observations[].metrics`.
|
|
50
|
+
- In `dashboard_spec.metric_schema`, mark windowed/snapshot metrics as `aggregation: "snapshot"` so Agent Performance does not sum them again.
|
|
51
|
+
- Include `observation_table` widgets for account period snapshots and video snapshots, with explicit columns for every recovered field.
|
|
52
|
+
7. If platform metrics are unavailable, record the missing access and ask for the relevant token/export instead of inventing numbers.
|
|
53
|
+
8. Summarize metrics by platform, account, device, content type, creative pattern, and issue/task id.
|
|
54
|
+
9. Write reusable learning as `agent_memory_delta` for channel agents.
|
|
55
|
+
|
|
56
|
+
## TikTok Dashboard Contract
|
|
57
|
+
|
|
58
|
+
TikTok recovery should include account-level and video-level tables in `dashboard_spec`:
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"schema_version": "swarm.dashboard.v1",
|
|
63
|
+
"title": "TikTok Data Collection",
|
|
64
|
+
"metric_schema": {
|
|
65
|
+
"post_views": { "label": "Post views", "aggregation": "snapshot" },
|
|
66
|
+
"profile_views": { "label": "Profile views", "aggregation": "snapshot" },
|
|
67
|
+
"views": { "label": "Views", "aggregation": "snapshot" },
|
|
68
|
+
"likes": { "label": "Likes", "aggregation": "snapshot" },
|
|
69
|
+
"comments": { "label": "Comments", "aggregation": "snapshot" },
|
|
70
|
+
"shares": { "label": "Shares", "aggregation": "snapshot" },
|
|
71
|
+
"saves": { "label": "Saves", "aggregation": "snapshot" },
|
|
72
|
+
"avg_watch_seconds": { "label": "Avg watch", "aggregation": "snapshot", "unit": "seconds" },
|
|
73
|
+
"watched_full_rate": { "label": "Watched full", "aggregation": "snapshot", "format": "percent" },
|
|
74
|
+
"new_followers": { "label": "New followers", "aggregation": "snapshot" }
|
|
75
|
+
},
|
|
76
|
+
"widgets": [
|
|
77
|
+
{
|
|
78
|
+
"id": "account_periods",
|
|
79
|
+
"title": "Account Period Snapshots",
|
|
80
|
+
"type": "table",
|
|
81
|
+
"query": {
|
|
82
|
+
"kind": "observation_table",
|
|
83
|
+
"platform": "tiktok_public",
|
|
84
|
+
"artifact_type": "account_period",
|
|
85
|
+
"latest_per_artifact": true,
|
|
86
|
+
"columns": [
|
|
87
|
+
{ "key": "period", "label": "Period", "source": "payload" },
|
|
88
|
+
{ "key": "date_range", "label": "Date range", "source": "payload" },
|
|
89
|
+
{ "key": "post_views", "label": "Post views", "source": "metric" },
|
|
90
|
+
{ "key": "profile_views", "label": "Profile views", "source": "metric" },
|
|
91
|
+
{ "key": "likes", "label": "Likes", "source": "metric" },
|
|
92
|
+
{ "key": "comments", "label": "Comments", "source": "metric" },
|
|
93
|
+
{ "key": "shares", "label": "Shares", "source": "metric" }
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"id": "video_snapshots",
|
|
99
|
+
"title": "Video Snapshots",
|
|
100
|
+
"type": "table",
|
|
101
|
+
"query": {
|
|
102
|
+
"kind": "observation_table",
|
|
103
|
+
"platform": "tiktok_public",
|
|
104
|
+
"artifact_type": "video",
|
|
105
|
+
"latest_per_artifact": true,
|
|
106
|
+
"columns": [
|
|
107
|
+
{ "key": "profile_order", "label": "Profile order", "source": "payload" },
|
|
108
|
+
{ "key": "posted_time", "label": "Posted time", "source": "payload" },
|
|
109
|
+
{ "key": "views", "label": "Views", "source": "metric" },
|
|
110
|
+
{ "key": "likes", "label": "Likes", "source": "metric" },
|
|
111
|
+
{ "key": "comments", "label": "Comments", "source": "metric" },
|
|
112
|
+
{ "key": "shares", "label": "Shares", "source": "metric" },
|
|
113
|
+
{ "key": "saves", "label": "Saves", "source": "metric" },
|
|
114
|
+
{ "key": "avg_watch_seconds", "label": "Avg watch", "source": "metric" },
|
|
115
|
+
{ "key": "watched_full_rate", "label": "Watched full", "source": "metric" },
|
|
116
|
+
{ "key": "new_followers", "label": "New followers", "source": "metric" }
|
|
117
|
+
]
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
```
|
|
47
123
|
|
|
48
124
|
## Output
|
|
49
125
|
Return recovered links/metrics, missing analytics access, record ids covered, data freshness, run log path, notable performance patterns, and follow-up tasks for planning.
|
package/package.json
CHANGED
package/src/commands/runtime.js
CHANGED
|
@@ -83,7 +83,6 @@ function runtimeDeps(overrides = {}) {
|
|
|
83
83
|
sleep: overrides.sleep || sleep,
|
|
84
84
|
syncKnowledge: overrides.syncKnowledge || syncKnowledge,
|
|
85
85
|
mcpKnowledgeSync: overrides.mcpKnowledgeSync || mcpKnowledgeSync,
|
|
86
|
-
cpuCount: overrides.cpuCount ?? os.cpus().length,
|
|
87
86
|
}
|
|
88
87
|
}
|
|
89
88
|
|
|
@@ -248,7 +247,7 @@ export async function scanRuntime(flags = {}) {
|
|
|
248
247
|
return scan
|
|
249
248
|
}
|
|
250
249
|
|
|
251
|
-
export async function registerRuntime(flags = {}, deps = {}) {
|
|
250
|
+
export async function registerRuntime(flags = {}, deps = {}, { maxConcurrent } = {}) {
|
|
252
251
|
const { buildRuntimeScan: scanBuilder, log, requestJson: request } = runtimeDeps(deps)
|
|
253
252
|
const config = configFromFlags(flags)
|
|
254
253
|
if (!config.token) throw new Error('GTM_WRITES_TOKEN or --token is required')
|
|
@@ -256,9 +255,13 @@ export async function registerRuntime(flags = {}, deps = {}) {
|
|
|
256
255
|
const scan = await scanBuilder({ env: scanEnvWithOverrides(flags), cliVersion: CLI_VERSION })
|
|
257
256
|
if (!scan.runtimes.length) throw new Error('no local AI runtimes detected on PATH')
|
|
258
257
|
|
|
258
|
+
const body = maxConcurrent
|
|
259
|
+
? { ...scan, runtimes: scan.runtimes.map(r => ({ ...r, max_concurrent: maxConcurrent })) }
|
|
260
|
+
: scan
|
|
261
|
+
|
|
259
262
|
const result = await request('/api/runtime/machines/register', {
|
|
260
263
|
method: 'POST',
|
|
261
|
-
body
|
|
264
|
+
body,
|
|
262
265
|
config,
|
|
263
266
|
})
|
|
264
267
|
log(JSON.stringify(result, null, 2))
|
|
@@ -1641,11 +1644,7 @@ async function claimAndRunRuntimeTasks(registration, flags, deps, handlerModule,
|
|
|
1641
1644
|
const config = configFromFlags(flags)
|
|
1642
1645
|
const machineKey = registration?.machine?.machine_key || machineOverride(flags) || ''
|
|
1643
1646
|
|
|
1644
|
-
|
|
1645
|
-
// maxConcurrent is honoured when it exceeds the CPU-derived default.
|
|
1646
|
-
const perRuntime = Math.max(1, (deps.cpuCount ?? os.cpus().length) - 1)
|
|
1647
|
-
const effectiveConcurrent = Math.max(maxConcurrent, runtimes.length * perRuntime)
|
|
1648
|
-
const slots = Array.from({ length: effectiveConcurrent }, (_, i) => runtimes[i % runtimes.length])
|
|
1647
|
+
const slots = Array.from({ length: maxConcurrent }, (_, i) => runtimes[i % runtimes.length])
|
|
1649
1648
|
const results = await Promise.allSettled(
|
|
1650
1649
|
slots.map(runtime => runOneRuntimeTaskSlot(runtime, config, machineKey, registration, flags, deps, handlerModule, retryState, heartbeatIntervalMs))
|
|
1651
1650
|
)
|
|
@@ -1659,14 +1658,11 @@ async function drainRuntimeTasks(registration, flags, deps, handlerModule, retry
|
|
|
1659
1658
|
const config = configFromFlags(flags)
|
|
1660
1659
|
const machineKey = registration?.machine?.machine_key || machineOverride(flags) || ''
|
|
1661
1660
|
|
|
1662
|
-
const perRuntime = Math.max(1, (deps.cpuCount ?? os.cpus().length) - 1)
|
|
1663
|
-
const effectiveConcurrent = Math.max(maxConcurrent, runtimes.length * perRuntime)
|
|
1664
|
-
|
|
1665
1661
|
let completed = 0
|
|
1666
1662
|
// Worker pool: each worker loops claiming tasks until the queue is empty,
|
|
1667
1663
|
// so a fast worker immediately picks up the next task without waiting for
|
|
1668
1664
|
// slow workers to finish (unlike the old batched Promise.allSettled approach).
|
|
1669
|
-
const workers = Array.from({ length:
|
|
1665
|
+
const workers = Array.from({ length: maxConcurrent }, (_, i) => {
|
|
1670
1666
|
const runtime = runtimes[i % runtimes.length]
|
|
1671
1667
|
return (async () => {
|
|
1672
1668
|
while (true) {
|
|
@@ -1686,11 +1682,9 @@ export async function startRuntimeDaemon(flags = {}, deps = {}) {
|
|
|
1686
1682
|
const scanIntervalMs = Number(flag(flags, 'scan-interval', '60')) * 1000
|
|
1687
1683
|
const taskIntervalMs = Number(flag(flags, 'task-interval', flag(flags, 'heartbeat-interval', '15'))) * 1000
|
|
1688
1684
|
const projectRefreshIntervalMs = Number(flag(flags, 'project-refresh-interval', '1800')) * 1000
|
|
1689
|
-
const cpuCount = resolvedDeps.cpuCount ?? os.cpus().length
|
|
1690
|
-
const cpuDefault = Math.max(1, cpuCount - 1)
|
|
1691
1685
|
const envMax = Number(process.env.DAEMON_MAX_CONCURRENCY)
|
|
1692
|
-
const envConcurrency = Number.isInteger(envMax) && envMax > 0
|
|
1693
|
-
const maxConcurrent = envConcurrency ?? Math.max(1, Number(flag(flags, 'concurrency',
|
|
1686
|
+
const envConcurrency = Number.isInteger(envMax) && envMax > 0 ? envMax : null
|
|
1687
|
+
const maxConcurrent = envConcurrency ?? Math.max(1, Number(flag(flags, 'concurrency', '10')) || 10)
|
|
1694
1688
|
const once = Boolean(flags.once)
|
|
1695
1689
|
const handlerPath = flag(flags, 'handler')
|
|
1696
1690
|
|
|
@@ -1711,7 +1705,7 @@ export async function startRuntimeDaemon(flags = {}, deps = {}) {
|
|
|
1711
1705
|
const retryState = createRetryState()
|
|
1712
1706
|
const uninstallExitHandlers = installCurrentClaimExitHandlers(flags, resolvedDeps)
|
|
1713
1707
|
try {
|
|
1714
|
-
let registration = await runWithDaemonRetry('register runtime', () => registerRuntime(flags, resolvedDeps), resolvedDeps, retryState)
|
|
1708
|
+
let registration = await runWithDaemonRetry('register runtime', () => registerRuntime(flags, resolvedDeps, { maxConcurrent }), resolvedDeps, retryState)
|
|
1715
1709
|
await runWithDaemonRetry('recover current claimed task', () => failAllPersistedClaims(
|
|
1716
1710
|
flags,
|
|
1717
1711
|
resolvedDeps,
|
package/src/schema.js
CHANGED
|
@@ -26,7 +26,9 @@ export function validateTelemetryBatch(input) {
|
|
|
26
26
|
}
|
|
27
27
|
const artifacts = Array.isArray(input.artifacts) ? input.artifacts : []
|
|
28
28
|
const observations = Array.isArray(input.observations) ? input.observations : []
|
|
29
|
-
if (!artifacts.length && !observations.length
|
|
29
|
+
if (!artifacts.length && !observations.length && !isObject(input.dashboard_spec)) {
|
|
30
|
+
return fail('artifacts, observations, or dashboard_spec are required')
|
|
31
|
+
}
|
|
30
32
|
for (let i = 0; i < artifacts.length; i += 1) {
|
|
31
33
|
const item = artifacts[i]
|
|
32
34
|
for (const field of ['platform', 'artifact_type', 'external_id', 'created_at']) {
|
|
@@ -48,7 +50,7 @@ export function validateTelemetryBatch(input) {
|
|
|
48
50
|
return { ok: true, batch: { ...input, agent_id: agentId.trim() } }
|
|
49
51
|
}
|
|
50
52
|
|
|
51
|
-
export function buildTelemetryBatch({ workspace, agent_id, agent_key, node_id, artifacts = [], observations = [] }) {
|
|
53
|
+
export function buildTelemetryBatch({ workspace, agent_id, agent_key, node_id, artifacts = [], observations = [], dashboard_spec = null }) {
|
|
52
54
|
return {
|
|
53
55
|
schema_version: TELEMETRY_SCHEMA_VERSION,
|
|
54
56
|
workspace,
|
|
@@ -58,5 +60,6 @@ export function buildTelemetryBatch({ workspace, agent_id, agent_key, node_id, a
|
|
|
58
60
|
sent_at: new Date().toISOString(),
|
|
59
61
|
artifacts,
|
|
60
62
|
observations,
|
|
63
|
+
...(dashboard_spec ? { dashboard_spec } : {}),
|
|
61
64
|
}
|
|
62
65
|
}
|