atris 3.37.1 → 3.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +25 -6
- package/README.md +2 -0
- package/atris/PERSONA.md +11 -11
- package/atris.md +17 -2
- package/ax +160 -34
- package/bin/atris.js +78 -6
- package/commands/activate.js +16 -3
- package/commands/agent-spawn.js +13 -18
- package/commands/autoland.js +76 -47
- package/commands/autopilot-front.js +0 -4
- package/commands/autopilot.js +0 -12
- package/commands/bench.js +10 -12
- package/commands/brain.js +0 -7
- package/commands/brief.js +0 -2
- package/commands/business-sync.js +0 -1
- package/commands/business.js +452 -12
- package/commands/chat-scan.js +5 -7
- package/commands/clean.js +31 -2
- package/commands/close.js +46 -11
- package/commands/cloud.js +0 -1
- package/commands/codex-goal.js +8 -12
- package/commands/computer.js +6 -5
- package/commands/console.js +64 -11
- package/commands/decide.js +166 -0
- package/commands/deck.js +30 -12
- package/commands/dream.js +0 -7
- package/commands/drill.js +14 -25
- package/commands/engine.js +206 -26
- package/commands/experiments.js +0 -4
- package/commands/fleet-report.js +12 -4
- package/commands/game.js +261 -0
- package/commands/gm.js +17 -7
- package/commands/goal.js +0 -6
- package/commands/harvest.js +1 -5
- package/commands/improve.js +29 -8
- package/commands/init.js +41 -37
- package/commands/integrations.js +0 -7
- package/commands/land.js +77 -20
- package/commands/launchpad.js +1 -16
- package/commands/lesson.js +17 -7
- package/commands/lifecycle.js +5 -5
- package/commands/log.js +55 -5
- package/commands/loop-front.js +0 -2
- package/commands/loops.js +6 -5
- package/commands/meet.js +0 -2
- package/commands/member.js +622 -573
- package/commands/mission.js +790 -314
- package/commands/next.js +0 -1
- package/commands/now.js +0 -12
- package/commands/one-lap.js +8 -8
- package/commands/orb.js +0 -4
- package/commands/pack-craft.js +0 -1
- package/commands/pack.js +3599 -424
- package/commands/play.js +6 -6
- package/commands/probe.js +12 -6
- package/commands/pulse.js +88 -64
- package/commands/push.js +0 -4
- package/commands/radar.js +14 -11
- package/commands/recover.js +340 -0
- package/commands/release.js +10 -9
- package/commands/report.js +0 -1
- package/commands/router.js +320 -0
- package/commands/run-front.js +0 -2
- package/commands/search.js +0 -3
- package/commands/site-deploy.js +870 -0
- package/commands/site.js +11 -2
- package/commands/skill.js +7 -2
- package/commands/slop.js +237 -7
- package/commands/status.js +6 -0
- package/commands/stream.js +4 -20
- package/commands/study.js +0 -3
- package/commands/sync-checkout.js +51 -0
- package/commands/sync.js +11 -40
- package/commands/task.js +999 -595
- package/commands/taste.js +101 -0
- package/commands/teach.js +86 -0
- package/commands/team.js +83 -3
- package/commands/unknowns.js +0 -10
- package/commands/update.js +0 -1
- package/commands/vercel.js +4 -2
- package/commands/visualize.js +0 -5
- package/commands/voice.js +195 -0
- package/commands/watch.js +1 -28
- package/commands/wiki.js +42 -15
- package/commands/wish.js +0 -1
- package/commands/workflow.js +2 -2
- package/commands/worktree.js +88 -28
- package/commands/xp.js +28 -30
- package/commands/youtube.js +0 -2
- package/lib/accept-verify-gate.js +126 -0
- package/lib/activity-stream.js +0 -2
- package/lib/arg-parser.js +41 -0
- package/lib/auto-accept-certified.js +261 -3
- package/lib/autoland.js +66 -3
- package/lib/ax-auto-lane.js +68 -7
- package/lib/bench/context.js +0 -2
- package/lib/bench/report.js +0 -1
- package/lib/bench/runner.js +18 -11
- package/lib/brief-ledger.js +0 -6
- package/lib/business-simulate.js +382 -0
- package/lib/chat-log-scan.js +0 -5
- package/lib/checkout-sync.js +52 -0
- package/lib/claude-boot-block.js +48 -0
- package/lib/cloud-mission.js +0 -6
- package/lib/codex-flight.js +0 -1
- package/lib/conductor-artifacts.js +25 -0
- package/lib/context-gatherer.js +7 -2
- package/lib/deck-compose.js +0 -1
- package/lib/deck-history.js +0 -1
- package/lib/deck-layout.js +0 -5
- package/lib/deck-review.js +0 -7
- package/lib/deck-schema.js +0 -1
- package/lib/default-verifier.js +0 -2
- package/lib/engine-registry.js +157 -28
- package/lib/experiments/daily.js +0 -6
- package/lib/falsifier-probe.js +84 -0
- package/lib/fleet.js +275 -42
- package/lib/functional-owner.js +0 -3
- package/lib/git-spawn.js +15 -0
- package/lib/inspect-fields.js +0 -1
- package/lib/json-file.js +37 -0
- package/lib/known-commands.js +2 -2
- package/lib/learnings.js +0 -2
- package/lib/lesson-preflight.js +146 -0
- package/lib/loop-doctor.js +11 -19
- package/lib/manifest.js +0 -2
- package/lib/member-alive.js +0 -1
- package/lib/member-switches.js +0 -5
- package/lib/mission-artifact.js +0 -1
- package/lib/mission-human-asks.js +28 -0
- package/lib/mission-protected-lane.js +296 -0
- package/lib/mission-room.js +0 -2
- package/lib/mission-runtime-loop.js +0 -1
- package/lib/next-moves.js +0 -18
- package/lib/official-cli-integration.js +47 -6
- package/lib/orb-context.js +14 -4
- package/lib/orb-scorecard.js +0 -1
- package/lib/outbound-send-gate.js +0 -6
- package/lib/pack-capabilities.js +685 -0
- package/lib/permission-grants.js +0 -6
- package/lib/policy-lessons.js +0 -2
- package/lib/pulse.js +68 -15
- package/lib/review-integrity.js +0 -2
- package/lib/router-brain.js +51 -3
- package/lib/runner-command.js +0 -10
- package/lib/runtime-bootstrap.js +0 -1
- package/lib/scorecard.js +0 -1
- package/lib/self-drive.js +45 -14
- package/lib/state-detection.js +0 -6
- package/lib/task-db.js +137 -7
- package/lib/task-decision.js +50 -0
- package/lib/taste-lessons.js +153 -0
- package/lib/team-presence.js +0 -1
- package/lib/tool-result-encode.js +17 -1
- package/lib/voice-gate.js +129 -2
- package/lib/wiki.js +152 -9
- package/lib/wish-audit.js +21 -7
- package/lib/wish-delegate.js +27 -21
- package/lib/wish-stats.js +0 -5
- package/lib/wish-store.js +0 -1
- package/lib/workspace-safety.js +0 -2
- package/lib/zip.js +95 -8
- package/package.json +2 -1
- package/templates/business-starter/persona.md +9 -0
package/commands/play.js
CHANGED
|
@@ -5,6 +5,7 @@ const os = require('os');
|
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const { spawnSync } = require('child_process');
|
|
7
7
|
const { getSessionProfile, loadCredentials } = require('../utils/auth');
|
|
8
|
+
const { hasFlag: hasExactFlag } = require('../lib/arg-parser');
|
|
8
9
|
|
|
9
10
|
const AGENTXP_LEADERBOARD_URL = 'https://api.atris.ai/api/agentxp/leaderboard';
|
|
10
11
|
const AGENTXP_GLOBAL_SYNC_RULE = 'Run atris login, then sync. Owner-provided sync tokens are guided-demo fallback only.';
|
|
@@ -38,8 +39,9 @@ function flag(args, name) {
|
|
|
38
39
|
return null;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
// This command treats --name=value as flag presence as well as a standalone flag.
|
|
43
|
+
function hasFlagOrValue(args, name) {
|
|
44
|
+
return hasExactFlag(args, name) || args.some(arg => arg.startsWith(`${name}=`));
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
function positional(args) {
|
|
@@ -284,7 +286,7 @@ function handleSourceLine(state) {
|
|
|
284
286
|
}
|
|
285
287
|
|
|
286
288
|
function ensureStarterMission(taskDb, db, workspaceRoot, player, tasks, args = []) {
|
|
287
|
-
if (
|
|
289
|
+
if (hasFlagOrValue(args, '--no-seed')) return { tasks, seeded: null };
|
|
288
290
|
if (selectMission(tasks, player)) return { tasks, seeded: null };
|
|
289
291
|
fs.mkdirSync(path.join(workspaceRoot, 'atris'), { recursive: true });
|
|
290
292
|
|
|
@@ -476,7 +478,7 @@ async function playCommand(...args) {
|
|
|
476
478
|
}
|
|
477
479
|
|
|
478
480
|
const state = modeState(args);
|
|
479
|
-
if (
|
|
481
|
+
if (hasFlagOrValue(args, '--json')) {
|
|
480
482
|
console.log(JSON.stringify(state, null, 2));
|
|
481
483
|
return;
|
|
482
484
|
}
|
|
@@ -485,6 +487,4 @@ async function playCommand(...args) {
|
|
|
485
487
|
|
|
486
488
|
module.exports = {
|
|
487
489
|
playCommand,
|
|
488
|
-
modeState,
|
|
489
|
-
selectMission,
|
|
490
490
|
};
|
package/commands/probe.js
CHANGED
|
@@ -26,9 +26,10 @@
|
|
|
26
26
|
|
|
27
27
|
const https = require('https');
|
|
28
28
|
const http = require('http');
|
|
29
|
+
const path = require('path');
|
|
29
30
|
const { getApiBaseUrl } = require('../utils/api');
|
|
30
31
|
const { loadCredentials } = require('../utils/auth');
|
|
31
|
-
const {
|
|
32
|
+
const { buildToolResultBody } = require('../lib/tool-result-encode');
|
|
32
33
|
|
|
33
34
|
// G2's honest blocked message (tool_policy_bench.MAX_TURNS_EXHAUSTED_MESSAGE)
|
|
34
35
|
// starts with this — an answer that is only this marker is a dead-end.
|
|
@@ -188,10 +189,13 @@ function runTerminal(base, token, command, businessId) {
|
|
|
188
189
|
// command runs in the mission's OWN workspace directory instead of the hosted
|
|
189
190
|
// ai-computer. Without this, a mission tick in a local workspace read the
|
|
190
191
|
// remote computer's files and reported local members (e.g. maze) as missing.
|
|
191
|
-
function runLocalTerminal(command, cwd) {
|
|
192
|
+
function runLocalTerminal(command, cwd, environment = process.env, pathPrefix = '') {
|
|
192
193
|
const { exec } = require('child_process');
|
|
193
194
|
const { envWithNodeDir } = require('../lib/spawn-env');
|
|
194
|
-
const
|
|
195
|
+
const baseEnv = envWithNodeDir(environment);
|
|
196
|
+
const env = pathPrefix
|
|
197
|
+
? { ...baseEnv, PATH: `${pathPrefix}${path.delimiter}${baseEnv.PATH || ''}` }
|
|
198
|
+
: baseEnv;
|
|
195
199
|
return new Promise((resolve) => {
|
|
196
200
|
exec(command, { cwd, env, timeout: 60000, maxBuffer: 4 * 1024 * 1024, shell: '/bin/bash' }, (error, stdout, stderr) => {
|
|
197
201
|
resolve({
|
|
@@ -237,6 +241,8 @@ async function runAtris2Turn(opts = {}) {
|
|
|
237
241
|
connectTimeoutMs = 60000,
|
|
238
242
|
signal = null,
|
|
239
243
|
localCwd = null,
|
|
244
|
+
localEnv = null,
|
|
245
|
+
localPathPrefix = '',
|
|
240
246
|
} = opts;
|
|
241
247
|
const t0 = Date.now();
|
|
242
248
|
const out = { ok: false, text: '', engine: null, tools_run: 0, cli_ops: [], unsupported: [], error: null, duration_ms: 0 };
|
|
@@ -322,7 +328,7 @@ async function runAtris2Turn(opts = {}) {
|
|
|
322
328
|
unsupported.push(`file_op:${op}`);
|
|
323
329
|
} else {
|
|
324
330
|
const term = localCwd
|
|
325
|
-
? await runLocalTerminal(cmd, localCwd)
|
|
331
|
+
? await runLocalTerminal(cmd, localCwd, localEnv || process.env, localPathPrefix)
|
|
326
332
|
: await runTerminal(base, token, cmd, business);
|
|
327
333
|
const ok = term.exit_code === 0;
|
|
328
334
|
out = ok
|
|
@@ -341,7 +347,7 @@ async function runAtris2Turn(opts = {}) {
|
|
|
341
347
|
unsupported.push(`cli_op:${op || '?'}`);
|
|
342
348
|
} else {
|
|
343
349
|
const term = localCwd
|
|
344
|
-
? await runLocalTerminal(cmd, localCwd)
|
|
350
|
+
? await runLocalTerminal(cmd, localCwd, localEnv || process.env, localPathPrefix)
|
|
345
351
|
: await runTerminal(base, token, cmd, business);
|
|
346
352
|
out = atrisCliResult(cmd, term);
|
|
347
353
|
cliOps.push(op || '?');
|
|
@@ -351,7 +357,7 @@ async function runAtris2Turn(opts = {}) {
|
|
|
351
357
|
unsupported.push(`tool:${name || '?'}`);
|
|
352
358
|
}
|
|
353
359
|
await postJson(`${base}/atris2/turn/tool-result`, token,
|
|
354
|
-
|
|
360
|
+
buildToolResultBody(ev.call_id || ev.id, out), 30000);
|
|
355
361
|
resetIdle();
|
|
356
362
|
}).catch((e) => reject(e));
|
|
357
363
|
} else if (et === 'result') {
|
package/commands/pulse.js
CHANGED
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
// `atris pulse` - the durable overnight self-improvement heartbeat for atris-cli.
|
|
4
4
|
//
|
|
5
|
-
// atris pulse tick run ONE self-improvement tick (what
|
|
5
|
+
// atris pulse tick run ONE self-improvement tick (what launchd calls)
|
|
6
6
|
// atris pulse status liveness, reward sum, ghost-tick detection
|
|
7
|
-
// atris pulse install write the
|
|
8
|
-
// atris pulse uninstall remove the
|
|
7
|
+
// atris pulse install write the tick script + install a launchd agent
|
|
8
|
+
// atris pulse uninstall remove the launchd agent
|
|
9
9
|
// atris pulse run run N ticks in the foreground (manual / testing)
|
|
10
10
|
//
|
|
11
11
|
// One tick: lock -> 'started' receipt -> run mission engine -> verify -> write
|
|
12
12
|
// 'finished' receipt (pulse_agi_loop_receipts.jsonl) + reward scorecard
|
|
13
13
|
// (scorecards.jsonl, gated) -> release lock. The whole point is that this fires
|
|
14
|
-
// from
|
|
14
|
+
// from launchd, so it self-improves overnight without Claude Code open.
|
|
15
15
|
|
|
16
16
|
const fs = require('fs');
|
|
17
17
|
const os = require('os');
|
|
@@ -19,11 +19,10 @@ const path = require('path');
|
|
|
19
19
|
const { spawnSync } = require('child_process');
|
|
20
20
|
const pulse = require('../lib/pulse');
|
|
21
21
|
const { DEFAULT_CLAUDE_RUNNER_MODEL } = require('../lib/runner-command');
|
|
22
|
+
const { hasFlag } = require('../lib/arg-parser');
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
function readFlag(args, name, fallback = null) {
|
|
24
|
+
// Preserve the existing rule that the next token is a value, even if it is a flag.
|
|
25
|
+
function readFollowingFlag(args, name, fallback = null) {
|
|
27
26
|
const i = args.indexOf(name);
|
|
28
27
|
if (i === -1 || i === args.length - 1) return fallback;
|
|
29
28
|
return args[i + 1];
|
|
@@ -41,8 +40,8 @@ Durable overnight self-improvement heartbeat.
|
|
|
41
40
|
Commands:
|
|
42
41
|
tick Run one heartbeat tick
|
|
43
42
|
status Show liveness, reward, and ghost-tick detection
|
|
44
|
-
install Install the
|
|
45
|
-
uninstall Remove the
|
|
43
|
+
install Install the launchd heartbeat
|
|
44
|
+
uninstall Remove the launchd heartbeat
|
|
46
45
|
run Run bounded ticks in the foreground
|
|
47
46
|
|
|
48
47
|
Options:
|
|
@@ -208,7 +207,7 @@ function tickCommand(args, root = process.cwd()) {
|
|
|
208
207
|
const asJson = wantsJson(args);
|
|
209
208
|
const noClaude = hasFlag(args, '--no-claude');
|
|
210
209
|
const noVerify = hasFlag(args, '--no-verify');
|
|
211
|
-
const verifyCmd = noVerify ? null :
|
|
210
|
+
const verifyCmd = noVerify ? null : readFollowingFlag(args, '--verify', pulse.defaultVerifyCmd(root));
|
|
212
211
|
const startedAt = Date.now();
|
|
213
212
|
|
|
214
213
|
// Detect a previous tick that died mid-run before we take the lock.
|
|
@@ -298,7 +297,12 @@ function tickCommand(args, root = process.cwd()) {
|
|
|
298
297
|
}
|
|
299
298
|
|
|
300
299
|
const elapsedMs = Date.now() - startedAt;
|
|
301
|
-
const reward = pulse.scoreTick({
|
|
300
|
+
const reward = pulse.scoreTick({
|
|
301
|
+
verifyPassed: verify.passed,
|
|
302
|
+
producedWork,
|
|
303
|
+
actorOk: engine.ok,
|
|
304
|
+
actorReason: engine.reason,
|
|
305
|
+
});
|
|
302
306
|
const changedTail = committed
|
|
303
307
|
? ' - committed'
|
|
304
308
|
: (changedFiles.length ? ` - ${changedFiles.length} file(s) changed` : '');
|
|
@@ -492,13 +496,21 @@ function statusCommand(args, root = process.cwd(), options = {}) {
|
|
|
492
496
|
const slot = pulse.resolvePulseSlot(root, options.homeDir ? { homeDir: options.homeDir } : {});
|
|
493
497
|
const receipts = pulse.readPulseReceipts(root);
|
|
494
498
|
const summary = pulse.summarizePulse(receipts);
|
|
495
|
-
const
|
|
499
|
+
const label = `com.atris.pulse.${slot.marker.toLowerCase().replace(/^atris_pulse_/, '').replace(/[^a-z0-9]+/g, '-')}`;
|
|
500
|
+
const homeDir = options.homeDir || os.homedir();
|
|
501
|
+
const plistPath = path.join(homeDir, 'Library', 'LaunchAgents', `${label}.plist`);
|
|
502
|
+
const legacyCronInstalled = cronInstalled(slot.markers, options.spawnSync || spawnSync);
|
|
503
|
+
const installed = fs.existsSync(plistPath) || legacyCronInstalled;
|
|
496
504
|
const expiryBreadcrumb = installed ? null : readPulseExpiryBreadcrumb(root);
|
|
497
505
|
const writeOutput = options.writeOutput || ((text) => process.stdout.write(text));
|
|
498
506
|
const out = {
|
|
499
507
|
ok: true,
|
|
500
508
|
action: 'pulse_status',
|
|
501
509
|
cron_installed: installed,
|
|
510
|
+
launchd_installed: fs.existsSync(plistPath),
|
|
511
|
+
legacy_cron_installed: legacyCronInstalled,
|
|
512
|
+
label,
|
|
513
|
+
plist_path: plistPath,
|
|
502
514
|
expiry_breadcrumb: expiryBreadcrumb,
|
|
503
515
|
state_home: slot.activeStateHome,
|
|
504
516
|
marker: slot.activeMarker,
|
|
@@ -509,7 +521,7 @@ function statusCommand(args, root = process.cwd(), options = {}) {
|
|
|
509
521
|
};
|
|
510
522
|
if (!asJson) {
|
|
511
523
|
writeOutput([
|
|
512
|
-
`pulse: ${installed ? '
|
|
524
|
+
`pulse: ${installed ? 'scheduler installed' : 'scheduler not installed (run: atris pulse install)'}`,
|
|
513
525
|
expiryBreadcrumb ? `heartbeat expired on ${expiryBreadcrumb.expired_at}; renew with: ${expiryBreadcrumb.renew_command}` : '',
|
|
514
526
|
`state home: ${slot.activeStateHome} marker: ${slot.activeMarker}`,
|
|
515
527
|
`ticks: ${summary.total_ticks} | reward: ${summary.reward_sum} | verify pass/fail: ${summary.verify_pass}/${summary.verify_fail}`,
|
|
@@ -564,7 +576,7 @@ function installCommand(args, root = process.cwd(), options = {}) {
|
|
|
564
576
|
const commandSpawn = options.spawnSync || spawnSync;
|
|
565
577
|
const writeOutput = options.writeOutput || ((text) => process.stdout.write(text));
|
|
566
578
|
const slot = pulse.resolvePulseSlot(root, options.homeDir ? { homeDir: options.homeDir } : {});
|
|
567
|
-
const cadenceInput =
|
|
579
|
+
const cadenceInput = readFollowingFlag(args, '--cadence', pulse.DEFAULT_CADENCE_CRON);
|
|
568
580
|
let cron;
|
|
569
581
|
try {
|
|
570
582
|
cron = pulse.normalizeCronCadence(cadenceInput);
|
|
@@ -582,8 +594,8 @@ function installCommand(args, root = process.cwd(), options = {}) {
|
|
|
582
594
|
let expiry;
|
|
583
595
|
try {
|
|
584
596
|
expiry = pulse.normalizeExpiryDuration({
|
|
585
|
-
hours:
|
|
586
|
-
days:
|
|
597
|
+
hours: readFollowingFlag(args, '--hours', null),
|
|
598
|
+
days: readFollowingFlag(args, '--days', '7'),
|
|
587
599
|
});
|
|
588
600
|
} catch (error) {
|
|
589
601
|
const out = {
|
|
@@ -591,23 +603,27 @@ function installCommand(args, root = process.cwd(), options = {}) {
|
|
|
591
603
|
action: 'pulse_install',
|
|
592
604
|
reason: 'invalid_expiry',
|
|
593
605
|
detail: error && error.message ? error.message : String(error),
|
|
594
|
-
hours:
|
|
595
|
-
days:
|
|
606
|
+
hours: readFollowingFlag(args, '--hours', null),
|
|
607
|
+
days: readFollowingFlag(args, '--days', '7'),
|
|
596
608
|
};
|
|
597
609
|
if (!asJson) process.stdout.write(`pulse install failed: ${out.detail}\n`);
|
|
598
610
|
return emit(out, asJson);
|
|
599
611
|
}
|
|
600
|
-
const verifyCmd =
|
|
601
|
-
const model =
|
|
602
|
-
const runnerProfile =
|
|
603
|
-
const runnerBin =
|
|
604
|
-
const runnerCommandTemplate =
|
|
612
|
+
const verifyCmd = readFollowingFlag(args, '--verify', pulse.defaultVerifyCmd(root));
|
|
613
|
+
const model = readFollowingFlag(args, '--model', process.env.ATRIS_RUNNER_MODEL || process.env.ATRIS_CLAUDE_MODEL || DEFAULT_CLAUDE_RUNNER_MODEL);
|
|
614
|
+
const runnerProfile = readFollowingFlag(args, '--runner-profile', process.env.ATRIS_RUNNER_PROFILE || '');
|
|
615
|
+
const runnerBin = readFollowingFlag(args, '--runner-bin', process.env.ATRIS_RUNNER_BIN || process.env.ATRIS_CLAUDE_BIN || '');
|
|
616
|
+
const runnerCommandTemplate = readFollowingFlag(args, '--runner-template', process.env.ATRIS_RUNNER_COMMAND_TEMPLATE || process.env.ATRIS_CLAUDE_COMMAND_TEMPLATE || '');
|
|
605
617
|
const deadlineEpoch = Math.floor(Date.now() / 1000) + expiry.seconds;
|
|
606
618
|
|
|
607
619
|
fs.mkdirSync(slot.stateHome, { recursive: true });
|
|
608
620
|
const scriptPath = path.join(slot.stateHome, 'tick.sh');
|
|
609
|
-
|
|
610
|
-
|
|
621
|
+
const label = `com.atris.pulse.${slot.marker.toLowerCase().replace(/^atris_pulse_/, '').replace(/[^a-z0-9]+/g, '-')}`;
|
|
622
|
+
const homeDir = options.homeDir || os.homedir();
|
|
623
|
+
const launchAgentsDir = path.join(homeDir, 'Library', 'LaunchAgents');
|
|
624
|
+
const plistPath = path.join(launchAgentsDir, `${label}.plist`);
|
|
625
|
+
// Resolve the real bin dirs the engine spawns by bare name, so launchd's
|
|
626
|
+
// minimal PATH doesn't break the worker spawn (claude lives in ~/.local/bin).
|
|
611
627
|
const pathDirs = resolveEngineBinDirs([runnerBin], commandSpawn);
|
|
612
628
|
const script = pulse.buildTickScript({
|
|
613
629
|
root,
|
|
@@ -621,18 +637,26 @@ function installCommand(args, root = process.cwd(), options = {}) {
|
|
|
621
637
|
runnerCommandTemplate,
|
|
622
638
|
verifyCmd,
|
|
623
639
|
pathDirs,
|
|
640
|
+
launchLabel: label,
|
|
641
|
+
launchPlistPath: plistPath,
|
|
624
642
|
});
|
|
625
643
|
fs.writeFileSync(scriptPath, script, { mode: 0o755 });
|
|
626
644
|
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
645
|
+
fs.mkdirSync(path.join(slot.stateHome, 'logs'), { recursive: true });
|
|
646
|
+
fs.mkdirSync(launchAgentsDir, { recursive: true });
|
|
647
|
+
fs.writeFileSync(plistPath, pulse.buildLaunchAgentPlist({ label, scriptPath, cron, stateHome: slot.stateHome }), 'utf8');
|
|
648
|
+
|
|
649
|
+
// Strip old cron entries while migrating existing installations.
|
|
630
650
|
const existing = commandSpawn('crontab', ['-l'], { encoding: 'utf8', timeout: 10000 });
|
|
631
651
|
const prior = existing.status === 0 ? String(existing.stdout || '') : '';
|
|
632
652
|
const cleaned = stripMarkedCrontabLines(prior, slot.markers);
|
|
633
|
-
const
|
|
634
|
-
|
|
653
|
+
const legacyHad = slot.markers.some((marker) => prior.includes(marker));
|
|
654
|
+
if (existing.status === 0 && legacyHad) commandSpawn('crontab', ['-'], { input: cleaned ? `${cleaned}\n` : '', encoding: 'utf8', timeout: 10000 });
|
|
655
|
+
const uid = options.uid != null ? options.uid : process.getuid();
|
|
656
|
+
commandSpawn('launchctl', ['bootout', `gui/${uid}/${label}`], { encoding: 'utf8', timeout: 10000 });
|
|
657
|
+
const apply = commandSpawn('launchctl', ['bootstrap', `gui/${uid}`, plistPath], { encoding: 'utf8', timeout: 10000 });
|
|
635
658
|
const expiryBreadcrumbCleared = apply.status === 0 && clearPulseExpiryBreadcrumb(root);
|
|
659
|
+
const cadenceMapping = pulse.launchIntervalFromCadence({ cron });
|
|
636
660
|
|
|
637
661
|
const out = {
|
|
638
662
|
ok: apply.status === 0,
|
|
@@ -641,7 +665,11 @@ function installCommand(args, root = process.cwd(), options = {}) {
|
|
|
641
665
|
state_home: slot.stateHome,
|
|
642
666
|
marker: slot.marker,
|
|
643
667
|
legacy_migrated: slot.legacyMatches,
|
|
644
|
-
|
|
668
|
+
label,
|
|
669
|
+
plist_path: plistPath,
|
|
670
|
+
crontab_line: null,
|
|
671
|
+
removed_legacy_crontab: legacyHad,
|
|
672
|
+
cadence_fallback: cadenceMapping.fallback,
|
|
645
673
|
cadence: cron,
|
|
646
674
|
cadence_input: cadenceInput,
|
|
647
675
|
expires_in_days: expiry.days,
|
|
@@ -656,46 +684,49 @@ function installCommand(args, root = process.cwd(), options = {}) {
|
|
|
656
684
|
if (!asJson) {
|
|
657
685
|
if (apply.status === 0) {
|
|
658
686
|
writeOutput([
|
|
659
|
-
`pulse installed. heartbeat fires '${cron}' against ${root}.`,
|
|
687
|
+
`pulse installed. launchd heartbeat fires '${cron}' against ${root}.`,
|
|
688
|
+
`label: ${label}`,
|
|
689
|
+
`plist: ${plistPath}`,
|
|
660
690
|
`script: ${scriptPath}`,
|
|
691
|
+
cadenceMapping.fallback ? `cadence '${cron}' is not directly mappable; using 3600 seconds.` : '',
|
|
661
692
|
`auto-expires in ${expiry.hours ? `${expiry.hours} hours` : `${expiry.days} days`}. stop early: atris pulse uninstall`,
|
|
662
|
-
].join('\n') + '\n');
|
|
693
|
+
].filter(Boolean).join('\n') + '\n');
|
|
663
694
|
} else {
|
|
664
|
-
writeOutput(`pulse install failed to
|
|
695
|
+
writeOutput(`pulse install failed to bootstrap ${label}: ${apply.stderr || apply.status}\nplist: ${plistPath}\n`);
|
|
665
696
|
}
|
|
666
697
|
}
|
|
667
698
|
return emit(out, asJson);
|
|
668
699
|
}
|
|
669
700
|
|
|
670
|
-
function uninstallCommand(args, root = process.cwd()) {
|
|
701
|
+
function uninstallCommand(args, root = process.cwd(), options = {}) {
|
|
671
702
|
const asJson = wantsJson(args);
|
|
672
|
-
const slot = pulse.resolvePulseSlot(root);
|
|
673
|
-
const
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
};
|
|
683
|
-
if (!asJson) process.stdout.write('pulse: no crontab to clean.\n');
|
|
684
|
-
return emit(out, asJson);
|
|
685
|
-
}
|
|
703
|
+
const slot = pulse.resolvePulseSlot(root, options.homeDir ? { homeDir: options.homeDir } : {});
|
|
704
|
+
const commandSpawn = options.spawnSync || spawnSync;
|
|
705
|
+
const homeDir = options.homeDir || os.homedir();
|
|
706
|
+
const label = `com.atris.pulse.${slot.marker.toLowerCase().replace(/^atris_pulse_/, '').replace(/[^a-z0-9]+/g, '-')}`;
|
|
707
|
+
const plistPath = path.join(homeDir, 'Library', 'LaunchAgents', `${label}.plist`);
|
|
708
|
+
const uid = options.uid != null ? options.uid : process.getuid();
|
|
709
|
+
commandSpawn('launchctl', ['bootout', `gui/${uid}/${label}`], { encoding: 'utf8', timeout: 10000 });
|
|
710
|
+
const plistHad = fs.existsSync(plistPath);
|
|
711
|
+
fs.rmSync(plistPath, { force: true });
|
|
712
|
+
const existing = commandSpawn('crontab', ['-l'], { encoding: 'utf8', timeout: 10000 });
|
|
686
713
|
const prior = String(existing.stdout || '');
|
|
687
714
|
const had = slot.markers.some((marker) => prior.includes(marker));
|
|
688
715
|
const cleaned = stripMarkedCrontabLines(prior, slot.markers);
|
|
689
|
-
|
|
716
|
+
let apply = { status: 0 };
|
|
717
|
+
if (existing.status === 0 && had) apply = commandSpawn('crontab', ['-'], { input: cleaned ? cleaned + '\n' : '', encoding: 'utf8', timeout: 10000 });
|
|
690
718
|
const out = {
|
|
691
719
|
ok: apply.status === 0,
|
|
692
720
|
action: 'pulse_uninstall',
|
|
693
|
-
removed: had,
|
|
721
|
+
removed: plistHad || had,
|
|
722
|
+
label,
|
|
723
|
+
plist_path: plistPath,
|
|
724
|
+
removed_legacy_crontab: had,
|
|
694
725
|
state_home: slot.activeStateHome,
|
|
695
726
|
marker: slot.activeMarker,
|
|
696
727
|
legacy_slot: slot.legacyMatches,
|
|
697
728
|
};
|
|
698
|
-
if (!asJson) process.stdout.write(had ?
|
|
729
|
+
if (!asJson) process.stdout.write(plistHad || had ? `pulse uninstalled (${label}).\n` : 'pulse: no heartbeat agent found.\n');
|
|
699
730
|
return emit(out, asJson);
|
|
700
731
|
}
|
|
701
732
|
|
|
@@ -703,7 +734,7 @@ function uninstallCommand(args, root = process.cwd()) {
|
|
|
703
734
|
|
|
704
735
|
function runCommand(args, root = process.cwd()) {
|
|
705
736
|
const asJson = wantsJson(args);
|
|
706
|
-
const maxTicks = Math.max(1, Number(
|
|
737
|
+
const maxTicks = Math.max(1, Number(readFollowingFlag(args, '--max-ticks', '1')) || 1);
|
|
707
738
|
const passthrough = args.filter((a) => a !== '--max-ticks' && a !== String(maxTicks));
|
|
708
739
|
const results = [];
|
|
709
740
|
for (let i = 0; i < maxTicks; i++) {
|
|
@@ -717,6 +748,9 @@ function pulseCommand(argv = []) {
|
|
|
717
748
|
const sub = argv[0];
|
|
718
749
|
const rest = argv.slice(1);
|
|
719
750
|
if (sub === '--help' || sub === '-h' || sub === 'help') return showHelp();
|
|
751
|
+
// `pulse <sub> --help` is a help request, not a run — never execute the
|
|
752
|
+
// subcommand (tick/status/etc. have real side effects) just to show usage.
|
|
753
|
+
if (rest.includes('--help') || rest.includes('-h')) return showHelp();
|
|
720
754
|
switch (sub) {
|
|
721
755
|
case 'tick':
|
|
722
756
|
return tickCommand(rest);
|
|
@@ -740,21 +774,11 @@ function pulseCommand(argv = []) {
|
|
|
740
774
|
|
|
741
775
|
module.exports = {
|
|
742
776
|
pulseCommand,
|
|
743
|
-
tickCommand,
|
|
744
777
|
statusCommand,
|
|
745
778
|
cronInstalled,
|
|
746
779
|
crontabHasActiveMarker,
|
|
747
780
|
pulseExpiryBreadcrumbPath,
|
|
748
|
-
readPulseExpiryBreadcrumb,
|
|
749
|
-
clearPulseExpiryBreadcrumb,
|
|
750
781
|
installCommand,
|
|
751
|
-
uninstallCommand,
|
|
752
782
|
runCommand,
|
|
753
|
-
runMissionEngine,
|
|
754
|
-
runAutopilotTick,
|
|
755
|
-
runEngine,
|
|
756
|
-
gitChangedFiles,
|
|
757
|
-
runVerify,
|
|
758
783
|
refreshOrbPolicyLessons,
|
|
759
|
-
stripMarkedCrontabLines,
|
|
760
784
|
};
|
package/commands/push.js
CHANGED
|
@@ -1055,7 +1055,6 @@ module.exports = {
|
|
|
1055
1055
|
buildPushUploadBatches,
|
|
1056
1056
|
shouldRetrySyncIndividually,
|
|
1057
1057
|
resolvePushSourceDir,
|
|
1058
|
-
canonicalWorkspaceRoot,
|
|
1059
1058
|
basenameOfManifestPath,
|
|
1060
1059
|
buildCloudHashMap,
|
|
1061
1060
|
isBusinessWorkspaceRoot,
|
|
@@ -1066,9 +1065,6 @@ module.exports = {
|
|
|
1066
1065
|
resolveChangedScopePaths,
|
|
1067
1066
|
mergePushScopes,
|
|
1068
1067
|
analyzePushSafety,
|
|
1069
|
-
isNestedWorkspacePollutionPath,
|
|
1070
|
-
isSyncReviewArtifactPath,
|
|
1071
|
-
renderPushSafetyBlock,
|
|
1072
1068
|
renderDriftBlock,
|
|
1073
1069
|
needsForceBroadWorkspaceConfirm,
|
|
1074
1070
|
promptConfirm,
|
package/commands/radar.js
CHANGED
|
@@ -396,16 +396,21 @@ function loadLoop(missions, root, deps) {
|
|
|
396
396
|
const events = readJsonLines(path.join(root, '.atris', 'state', 'mission_events.jsonl'), deps.readFile, deps.exists);
|
|
397
397
|
const codexGoal = readJsonFile(path.join(root, '.atris', 'state', 'codex_goal.json'), deps, {}) || {};
|
|
398
398
|
const tickEvents = events.filter(event => event.type === 'mission_tick');
|
|
399
|
+
// Live missions sit in planning/ready/running; counting only 'running'
|
|
400
|
+
// reported 0 while a worker was mid-flight (CLI-1183, seen live 2026-07-25).
|
|
401
|
+
const ACTIVE_MISSION_STATUSES = new Set(['planning', 'running', 'ready']);
|
|
402
|
+
const isActive = (mission) => ACTIVE_MISSION_STATUSES.has(String(mission.status || ''));
|
|
399
403
|
return {
|
|
400
|
-
running: missions.filter(
|
|
404
|
+
running: missions.filter(isActive).length,
|
|
405
|
+
paused: missions.filter(mission => mission.status === 'paused').length,
|
|
401
406
|
always_on: missions.filter(mission => mission.always_on).length,
|
|
402
407
|
stale: missions.filter(mission => mission.stale).length,
|
|
403
|
-
no_verifier: missions.filter(mission => mission
|
|
408
|
+
no_verifier: missions.filter(mission => isActive(mission) && !mission.verifier).length,
|
|
404
409
|
ticks: tickEvents.length,
|
|
405
410
|
last_event_at: events[events.length - 1]?.at || null,
|
|
406
411
|
codex_goal: codexGoal.goal?.objective || null,
|
|
407
412
|
codex_goal_mission: codexGoal.goal?.mission_id || null,
|
|
408
|
-
infinite_loop_risk: missions.some(mission => mission.stale || (mission
|
|
413
|
+
infinite_loop_risk: missions.some(mission => mission.stale || (isActive(mission) && !mission.verifier)),
|
|
409
414
|
};
|
|
410
415
|
}
|
|
411
416
|
|
|
@@ -735,7 +740,11 @@ function summarize(tasks, missions, worktrees, agents) {
|
|
|
735
740
|
review: count(tasks, t => t.status === 'review'),
|
|
736
741
|
certifiedReview: count(tasks, t => t.status === 'review' && t.metadata && t.metadata.agent_certified),
|
|
737
742
|
},
|
|
738
|
-
missions: {
|
|
743
|
+
missions: {
|
|
744
|
+
running: count(missions, m => ['planning', 'running', 'ready'].includes(String(m.status || ''))),
|
|
745
|
+
paused: count(missions, m => m.status === 'paused'),
|
|
746
|
+
stale: count(missions, m => m.stale),
|
|
747
|
+
},
|
|
739
748
|
worktrees: { total: worktrees.length, dirty: count(worktrees, w => Number(w.dirty) > 0) },
|
|
740
749
|
};
|
|
741
750
|
}
|
|
@@ -832,7 +841,7 @@ function renderRadar(data) {
|
|
|
832
841
|
lines.push('');
|
|
833
842
|
lines.push(`Agents: ${s.agents.active}/${s.agents.total} active`);
|
|
834
843
|
lines.push(`Tasks: ${s.tasks.open} open, ${s.tasks.claimed} claimed, ${s.tasks.review} review (${s.tasks.certifiedReview} certified)`);
|
|
835
|
-
lines.push(`Missions: ${s.missions.running}
|
|
844
|
+
lines.push(`Missions: ${s.missions.running} active, ${s.missions.paused || 0} paused, ${s.missions.stale} stale/no-verifier`);
|
|
836
845
|
lines.push(`Worktrees: ${s.worktrees.total} registered, ${s.worktrees.dirty} dirty`);
|
|
837
846
|
lines.push(`Next: ${data.next_action}`);
|
|
838
847
|
if (data.os) {
|
|
@@ -1188,16 +1197,10 @@ function radarCommand(args = [], options = {}) {
|
|
|
1188
1197
|
module.exports = {
|
|
1189
1198
|
agentTypeForCommand,
|
|
1190
1199
|
agentTopPayload,
|
|
1191
|
-
branchIdentityAtCwd,
|
|
1192
1200
|
collectRadar,
|
|
1193
|
-
isInteractiveClaudeCommand,
|
|
1194
|
-
loadAgentWorktreeSidecar,
|
|
1195
1201
|
parsePsOutput,
|
|
1196
|
-
parseSessionIdFromCommand,
|
|
1197
1202
|
parseWorktrees,
|
|
1198
1203
|
radarCommand,
|
|
1199
1204
|
renderAgentTop,
|
|
1200
1205
|
renderRadar,
|
|
1201
|
-
resolveAgentTaskBinding,
|
|
1202
|
-
taskForOwner,
|
|
1203
1206
|
};
|