autark-cli 0.5.1 → 0.5.3
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/autark.mjs +113 -9
- package/package.json +1 -1
package/autark.mjs
CHANGED
|
@@ -320,7 +320,7 @@ function settingsUsage() {
|
|
|
320
320
|
// via pnpm. Re-exec autark if its own version moved.
|
|
321
321
|
// 1. GET /v1/me — compare runtime_version + agentmail_email against local.
|
|
322
322
|
// 2. If runtime_version differs: curl the canonical files from the repo,
|
|
323
|
-
// refresh the 6 kstack skills via pnpm dlx, reload
|
|
323
|
+
// refresh the 6 kstack skills via pnpm dlx, reload the local scheduler, stamp the
|
|
324
324
|
// new version into ~/.autark/runtime/version.txt.
|
|
325
325
|
// 3. If agentmail_email differs: POST /v1/inbox/rotate to re-mint a fresh
|
|
326
326
|
// inbox-scoped key, rewrite credentials.json.
|
|
@@ -413,8 +413,8 @@ async function update(rest) {
|
|
|
413
413
|
await refreshRuntimeFiles()
|
|
414
414
|
log('refreshing kstack skills…')
|
|
415
415
|
await refreshSkills()
|
|
416
|
-
log('reloading
|
|
417
|
-
await
|
|
416
|
+
log('reloading local scheduler…')
|
|
417
|
+
await reloadLocalScheduler()
|
|
418
418
|
// Only stamp the version AFTER every step succeeded.
|
|
419
419
|
writeLocalVersion(me.runtime_version)
|
|
420
420
|
sub(`stamped ~/.autark/runtime/version.txt → ${me.runtime_version}`)
|
|
@@ -512,13 +512,13 @@ async function refreshRuntimeFiles() {
|
|
|
512
512
|
const agentSh = path.join(AUTARK_HOME, 'agent.sh')
|
|
513
513
|
await curlFile(`${RAW_RUNTIME_BASE}/agent.sh`, agentSh)
|
|
514
514
|
fs.chmodSync(agentSh, 0o755)
|
|
515
|
+
const agentPs1 = path.join(AUTARK_HOME, 'agent.ps1')
|
|
516
|
+
await curlFile(`${RAW_RUNTIME_BASE}/agent.ps1`, agentPs1)
|
|
515
517
|
}
|
|
516
518
|
|
|
517
519
|
async function refreshSkills() {
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
if (res.status !== 0) throw new Error(`skill ${name} install failed`)
|
|
521
|
-
}
|
|
520
|
+
const res = await spawnSync('pnpm', ['dlx', '--silent', 'skills', 'add', 'kiluazen/kstack', '-s', SKILL_NAMES.join(','), '-y', '-g'])
|
|
521
|
+
if (res.status !== 0) throw new Error(`kstack skills install failed`)
|
|
522
522
|
}
|
|
523
523
|
|
|
524
524
|
async function reloadLaunchd() {
|
|
@@ -549,6 +549,29 @@ async function reloadLaunchd() {
|
|
|
549
549
|
await spawnSync('launchctl', ['load', plist])
|
|
550
550
|
}
|
|
551
551
|
|
|
552
|
+
async function reloadWindowsTask() {
|
|
553
|
+
if (process.platform !== 'win32') return
|
|
554
|
+
const canonicalHome = path.join(os.homedir(), '.autark')
|
|
555
|
+
if (path.resolve(AUTARK_HOME) !== path.resolve(canonicalHome)) {
|
|
556
|
+
return
|
|
557
|
+
}
|
|
558
|
+
const agentPs1 = path.join(AUTARK_HOME, 'agent.ps1')
|
|
559
|
+
if (!fs.existsSync(agentPs1)) return
|
|
560
|
+
const taskName = 'Autark Runner'
|
|
561
|
+
const ps = [
|
|
562
|
+
`$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument '-NoProfile -ExecutionPolicy Bypass -File "${agentPs1.replaceAll('"', '`"')}"'`,
|
|
563
|
+
`$morning = New-ScheduledTaskTrigger -Daily -At 9am`,
|
|
564
|
+
`$evening = New-ScheduledTaskTrigger -Daily -At 7pm`,
|
|
565
|
+
`Register-ScheduledTask -TaskName '${taskName}' -Action $action -Trigger @($morning, $evening) -Description 'Autark daily runner' -Force | Out-Null`,
|
|
566
|
+
].join('; ')
|
|
567
|
+
await spawnSync('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', ps])
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
async function reloadLocalScheduler() {
|
|
571
|
+
await reloadLaunchd()
|
|
572
|
+
await reloadWindowsTask()
|
|
573
|
+
}
|
|
574
|
+
|
|
552
575
|
// Promise-wrapped spawn — returns { status: number }. stdio inherited so the
|
|
553
576
|
// user sees pnpm's output. Used for self-update + skills refresh.
|
|
554
577
|
function spawnSync(cmd, args) {
|
|
@@ -925,6 +948,7 @@ async function mailSend(rest) {
|
|
|
925
948
|
subject,
|
|
926
949
|
response: result,
|
|
927
950
|
})
|
|
951
|
+
noteAutoLog(action, opts)
|
|
928
952
|
printJson({ ...result, autark_action_id: action?.id })
|
|
929
953
|
}
|
|
930
954
|
|
|
@@ -942,6 +966,7 @@ async function mailReply(rest, mode) {
|
|
|
942
966
|
response: result,
|
|
943
967
|
metadata: { message_id: messageId },
|
|
944
968
|
})
|
|
969
|
+
noteAutoLog(action, opts)
|
|
945
970
|
printJson({ ...result, autark_action_id: action?.id })
|
|
946
971
|
}
|
|
947
972
|
|
|
@@ -959,9 +984,20 @@ async function mailForward(rest) {
|
|
|
959
984
|
response: result,
|
|
960
985
|
metadata: { message_id: messageId },
|
|
961
986
|
})
|
|
987
|
+
noteAutoLog(action, opts)
|
|
962
988
|
printJson({ ...result, autark_action_id: action?.id })
|
|
963
989
|
}
|
|
964
990
|
|
|
991
|
+
// Explicit human-readable cue so agents (and humans) see that the action
|
|
992
|
+
// landed in autark and don't follow up with a redundant `autark log action`
|
|
993
|
+
// for the same send. Stays on stderr so JSON consumers piping stdout aren't
|
|
994
|
+
// affected. Only fires when --run-id was passed and the log succeeded.
|
|
995
|
+
function noteAutoLog(action, opts) {
|
|
996
|
+
if (!action?.id) return
|
|
997
|
+
const runId = opts['run-id'] || opts.run_id || opts.run
|
|
998
|
+
console.error(`autark: auto-logged action ${action.id} (run ${runId}) — do NOT run \`autark log action\` for this`)
|
|
999
|
+
}
|
|
1000
|
+
|
|
965
1001
|
async function mailThreads(rest) {
|
|
966
1002
|
const opts = parseArgs(rest)
|
|
967
1003
|
const creds = requireAgentmailCredentials()
|
|
@@ -1126,6 +1162,21 @@ function actionShortId(id) {
|
|
|
1126
1162
|
return String(id || '').slice(0, 8)
|
|
1127
1163
|
}
|
|
1128
1164
|
|
|
1165
|
+
// Aggregate sends + replies per channel. The cron writes inbound_count onto
|
|
1166
|
+
// each action; agents use this to score hypotheses without re-walking threads.
|
|
1167
|
+
// Channels are kept separate (email vs github vs reddit vs hn vs substack) —
|
|
1168
|
+
// a public comment reply is a different signal class than an email reply.
|
|
1169
|
+
function channelBreakdown(actions) {
|
|
1170
|
+
const by = {}
|
|
1171
|
+
for (const a of (actions || [])) {
|
|
1172
|
+
const ch = a.channel || 'unknown'
|
|
1173
|
+
if (!by[ch]) by[ch] = { sent: 0, replied: 0 }
|
|
1174
|
+
by[ch].sent += 1
|
|
1175
|
+
if ((a.inbound_count || 0) > 0) by[ch].replied += 1
|
|
1176
|
+
}
|
|
1177
|
+
return by
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1129
1180
|
function printProductContext(r) {
|
|
1130
1181
|
// ---- structured key-block ----
|
|
1131
1182
|
kv('product.id', r.product.id)
|
|
@@ -1140,6 +1191,15 @@ function printProductContext(r) {
|
|
|
1140
1191
|
kv(`hypothesis.${h.code}.title`, h.title)
|
|
1141
1192
|
kv(`hypothesis.${h.code}.status`, h.status)
|
|
1142
1193
|
kv(`hypothesis.${h.code}.run_count`, h.run_count)
|
|
1194
|
+
kv(`hypothesis.${h.code}.action_count`, h.action_count)
|
|
1195
|
+
kv(`hypothesis.${h.code}.reply_count`, h.reply_count)
|
|
1196
|
+
if (h.needs_human_count) kv(`hypothesis.${h.code}.needs_human_count`, h.needs_human_count)
|
|
1197
|
+
const actions = (h.runs || []).flatMap((run) => run.actions || [])
|
|
1198
|
+
const by = channelBreakdown(actions)
|
|
1199
|
+
for (const ch of Object.keys(by).sort()) {
|
|
1200
|
+
kv(`hypothesis.${h.code}.channel.${ch}.sent`, by[ch].sent)
|
|
1201
|
+
kv(`hypothesis.${h.code}.channel.${ch}.replied`, by[ch].replied)
|
|
1202
|
+
}
|
|
1143
1203
|
}
|
|
1144
1204
|
console.log('---')
|
|
1145
1205
|
// ---- narrative ----
|
|
@@ -1152,7 +1212,13 @@ function printProductContext(r) {
|
|
|
1152
1212
|
} else {
|
|
1153
1213
|
console.log(`\n## Hypotheses (${r.hypotheses.length})\n`)
|
|
1154
1214
|
for (const h of r.hypotheses) {
|
|
1155
|
-
|
|
1215
|
+
const actions = (h.runs || []).flatMap((run) => run.actions || [])
|
|
1216
|
+
const by = channelBreakdown(actions)
|
|
1217
|
+
const channelSummary = Object.keys(by).sort()
|
|
1218
|
+
.map((ch) => `${ch} ${by[ch].replied}/${by[ch].sent}`)
|
|
1219
|
+
.join(' ')
|
|
1220
|
+
const tail = channelSummary ? ` — replies/sends by channel: ${channelSummary}` : ''
|
|
1221
|
+
console.log(`- [${h.status}] ${h.code} — ${h.title} (runs: ${h.run_count}, actions: ${h.action_count || 0}, replies: ${h.reply_count || 0})${tail}`)
|
|
1156
1222
|
}
|
|
1157
1223
|
}
|
|
1158
1224
|
}
|
|
@@ -1165,6 +1231,27 @@ function printHypothesisContext(result) {
|
|
|
1165
1231
|
kv('hypothesis.code', result.hypothesis.code)
|
|
1166
1232
|
kv('hypothesis.title', result.hypothesis.title)
|
|
1167
1233
|
kv('hypothesis.status', result.hypothesis.status)
|
|
1234
|
+
// Feedback — operator nudges left on this hypothesis, oldest first. The
|
|
1235
|
+
// worker returns them; previously the CLI silently dropped them, so agents
|
|
1236
|
+
// never saw the operator's running corrections. Now surface them up top
|
|
1237
|
+
// (before runs/actions) so the agent reads them at the start of every run.
|
|
1238
|
+
for (const f of (result.feedback || [])) {
|
|
1239
|
+
const shortF = actionShortId(f.id)
|
|
1240
|
+
kv(`feedback.${shortF}.created_at`, f.created_at)
|
|
1241
|
+
if (f.action_id) kv(`feedback.${shortF}.action_id`, f.action_id)
|
|
1242
|
+
kv(`feedback.${shortF}.text`, f.text)
|
|
1243
|
+
}
|
|
1244
|
+
// Per-channel rollup across all runs on this hypothesis. Lets the agent
|
|
1245
|
+
// grep `hypothesis.channel.*` to see what's actually landing without
|
|
1246
|
+
// counting actions itself.
|
|
1247
|
+
const allActions = (result.runs || []).flatMap((run) => run.actions || [])
|
|
1248
|
+
kv('hypothesis.action_count', allActions.length)
|
|
1249
|
+
kv('hypothesis.reply_count', allActions.filter((a) => (a.inbound_count || 0) > 0).length)
|
|
1250
|
+
const channelTotals = channelBreakdown(allActions)
|
|
1251
|
+
for (const ch of Object.keys(channelTotals).sort()) {
|
|
1252
|
+
kv(`hypothesis.channel.${ch}.sent`, channelTotals[ch].sent)
|
|
1253
|
+
kv(`hypothesis.channel.${ch}.replied`, channelTotals[ch].replied)
|
|
1254
|
+
}
|
|
1168
1255
|
for (const run of (result.runs || [])) {
|
|
1169
1256
|
const shortRun = actionShortId(run.id)
|
|
1170
1257
|
kv(`run.${shortRun}.id`, run.id)
|
|
@@ -1182,6 +1269,11 @@ function printHypothesisContext(result) {
|
|
|
1182
1269
|
if (a.agentmail_thread_id) kv(`action.${shortA}.agentmail_thread_id`, a.agentmail_thread_id)
|
|
1183
1270
|
if (a.agentmail_inbox_id) kv(`action.${shortA}.agentmail_inbox_id`, a.agentmail_inbox_id)
|
|
1184
1271
|
if (a.occurred_at) kv(`action.${shortA}.occurred_at`, a.occurred_at)
|
|
1272
|
+
kv(`action.${shortA}.inbound_count`, a.inbound_count || 0)
|
|
1273
|
+
if (a.last_inbound_at) kv(`action.${shortA}.last_inbound_at`, a.last_inbound_at)
|
|
1274
|
+
if (a.reply_checked_at) kv(`action.${shortA}.reply_checked_at`, a.reply_checked_at)
|
|
1275
|
+
if (a.needs_human) kv(`action.${shortA}.needs_human`, a.needs_human)
|
|
1276
|
+
if (a.escalation_reason) kv(`action.${shortA}.escalation_reason`, a.escalation_reason)
|
|
1185
1277
|
// Spread well-known metadata keys to their own lines for greppability.
|
|
1186
1278
|
const md = a.metadata || {}
|
|
1187
1279
|
for (const [mk, mv] of Object.entries(md)) {
|
|
@@ -1195,6 +1287,14 @@ function printHypothesisContext(result) {
|
|
|
1195
1287
|
// ---- narrative ----
|
|
1196
1288
|
console.log(`# ${result.product.slug}/${result.hypothesis.code} — ${result.hypothesis.title}\n`)
|
|
1197
1289
|
console.log(result.hypothesis.hypothesis_md)
|
|
1290
|
+
if ((result.feedback || []).length > 0) {
|
|
1291
|
+
console.log(`\n\n## Feedback (operator nudges, oldest first)\n`)
|
|
1292
|
+
for (const f of result.feedback) {
|
|
1293
|
+
const when = f.created_at || ''
|
|
1294
|
+
const tag = f.action_id ? ` (on action ${f.action_id})` : ''
|
|
1295
|
+
console.log(`- [${when}]${tag} ${f.text}`)
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1198
1298
|
for (const run of (result.runs || [])) {
|
|
1199
1299
|
console.log(`\n\n## Run ${run.run_number} (${run.id})`)
|
|
1200
1300
|
console.log(`started: ${run.started_at}${run.finished_at ? ` finished: ${run.finished_at}` : ' (in progress)'}`)
|
|
@@ -1202,7 +1302,11 @@ function printHypothesisContext(result) {
|
|
|
1202
1302
|
console.log(`\nActions:`)
|
|
1203
1303
|
for (const a of run.actions) {
|
|
1204
1304
|
const pointer = a.url || a.agentmail_thread_id || a.recipient || ''
|
|
1205
|
-
|
|
1305
|
+
const flags = []
|
|
1306
|
+
if ((a.inbound_count || 0) > 0) flags.push(`replied×${a.inbound_count}`)
|
|
1307
|
+
if (a.needs_human) flags.push('needs_human')
|
|
1308
|
+
const flagStr = flags.length ? ` [${flags.join(', ')}]` : ''
|
|
1309
|
+
console.log(` [${a.channel}] ${a.title}${pointer ? ` → ${pointer}` : ''}${flagStr}`)
|
|
1206
1310
|
}
|
|
1207
1311
|
}
|
|
1208
1312
|
if (run.narrative_md) console.log(`\n${run.narrative_md}`)
|
package/package.json
CHANGED