atris 3.45.1 → 3.46.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 +19 -3
- package/FOR_AGENTS.md +5 -0
- package/README.md +2 -2
- package/atris/AGENTS.md +4 -3
- package/atris/CLAUDE.md +1 -1
- package/atris/atris.md +21 -1
- package/atris/skills/atris/SKILL.md +7 -3
- package/atris/skills/loop/SKILL.md +6 -3
- package/atris.md +18 -1
- package/bin/atris.js +5 -0
- package/commands/autoland.js +25 -3
- package/commands/brain.js +14 -3
- package/commands/ci.js +44 -0
- package/commands/codex-goal.js +21 -119
- package/commands/init.js +33 -10
- package/commands/mission.js +116 -53
- package/commands/pack.js +90 -25
- package/commands/sync.js +34 -6
- package/commands/task.js +239 -31
- package/commands/voice.js +12 -2
- package/lib/ci-runner.js +397 -0
- package/lib/engine-validate.js +141 -1
- package/lib/known-commands.js +1 -1
- package/lib/task-db.js +22 -3
- package/lib/task-explanation.js +229 -0
- package/lib/todo-fallback.js +6 -0
- package/lib/voice-card.js +258 -0
- package/package.json +1 -1
- package/templates/research-canonical/atris.md +6 -0
package/commands/mission.js
CHANGED
|
@@ -98,6 +98,7 @@ const GOAL_LOOP_STATUSES = new Set(['planning', 'running', 'ready']);
|
|
|
98
98
|
const STATUS_ALIASES = new Set(['active']);
|
|
99
99
|
const CODEX_NATIVE_GOAL_SLOT_STATUSES = new Set(['active', 'paused', 'usage_limited']);
|
|
100
100
|
const CODEX_NATIVE_GOAL_REPLACE_STATUSES = new Set(['active', 'paused', 'usage_limited']);
|
|
101
|
+
const CODEX_NATIVE_GOAL_CLOSED_STATUSES = new Set(['complete', 'completed', 'achieved']);
|
|
101
102
|
const DEFAULT_LONG_RUN_VERIFIER = 'git diff --check';
|
|
102
103
|
const SLEEP_LENGTH_BUDGET_SECONDS = 3600;
|
|
103
104
|
const HUMAN_BLOCKING_PAUSE_REASONS = new Set(['auth-required', 'model-unavailable', 'rate-limit-exceeded-wall']);
|
|
@@ -6552,9 +6553,14 @@ function normalizeCodexNativeGoalStatus(value) {
|
|
|
6552
6553
|
if (compact === 'usagelimited') return 'usage_limited';
|
|
6553
6554
|
if (compact === 'active') return 'active';
|
|
6554
6555
|
if (compact === 'paused') return 'paused';
|
|
6556
|
+
if (CODEX_NATIVE_GOAL_CLOSED_STATUSES.has(compact)) return 'complete';
|
|
6555
6557
|
return raw.toLowerCase();
|
|
6556
6558
|
}
|
|
6557
6559
|
|
|
6560
|
+
function codexRuntimeTaskIsClosed(runtimeGoalState) {
|
|
6561
|
+
return runtimeGoalState?.status === 'complete';
|
|
6562
|
+
}
|
|
6563
|
+
|
|
6558
6564
|
function codexRuntimeGoalStatusLabel(runtimeGoalState) {
|
|
6559
6565
|
const status = runtimeGoalState?.status || '';
|
|
6560
6566
|
if (status === 'usage_limited') return 'usageLimited';
|
|
@@ -6652,9 +6658,7 @@ function codexNativeGoalRuntimeReplaceAction(newMission, runtimeGoalState = null
|
|
|
6652
6658
|
const toObjective = codexGoalObjective(newMission);
|
|
6653
6659
|
const fromObjective = runtimeGoalState?.objective || null;
|
|
6654
6660
|
const ackNewMission = commands.ack_new_mission || codexGoalAckCommand(newMission, toObjective);
|
|
6655
|
-
const
|
|
6656
|
-
const createNewGoal = `create_goal({ objective: ${JSON.stringify(toObjective)} })`;
|
|
6657
|
-
const supersedeApproved = commands.allow_native_goal_supersede === true;
|
|
6661
|
+
const newTaskInstruction = `Create a new Codex task for ${JSON.stringify(toObjective)}, then run ${ackNewMission}`;
|
|
6658
6662
|
return {
|
|
6659
6663
|
runtime: 'codex',
|
|
6660
6664
|
tool: 'replace_goal',
|
|
@@ -6672,23 +6676,18 @@ function codexNativeGoalRuntimeReplaceAction(newMission, runtimeGoalState = null
|
|
|
6672
6676
|
ack_new_mission: ackNewMission,
|
|
6673
6677
|
},
|
|
6674
6678
|
fallback: {
|
|
6675
|
-
reason: '
|
|
6676
|
-
automatic:
|
|
6677
|
-
approved:
|
|
6678
|
-
executable_now:
|
|
6679
|
-
blocked_by:
|
|
6680
|
-
safe_when: '
|
|
6681
|
-
sequence_name: '
|
|
6682
|
-
sequence: [
|
|
6683
|
-
completeCurrentGoal,
|
|
6684
|
-
createNewGoal,
|
|
6685
|
-
ackNewMission,
|
|
6686
|
-
],
|
|
6679
|
+
reason: 'A different objective belongs in a new Codex task; this task keeps its current goal and history.',
|
|
6680
|
+
automatic: false,
|
|
6681
|
+
approved: false,
|
|
6682
|
+
executable_now: false,
|
|
6683
|
+
blocked_by: 'new_codex_task_required',
|
|
6684
|
+
safe_when: 'Continue here only when the active objective still matches this task.',
|
|
6685
|
+
sequence_name: 'new_codex_task_required',
|
|
6686
|
+
sequence: [],
|
|
6687
6687
|
commands: {
|
|
6688
6688
|
...commands,
|
|
6689
|
-
|
|
6690
|
-
|
|
6691
|
-
ack_new_mission: ackNewMission,
|
|
6689
|
+
create_new_task: newTaskInstruction,
|
|
6690
|
+
ack_new_mission_after_task_create: ackNewMission,
|
|
6692
6691
|
},
|
|
6693
6692
|
},
|
|
6694
6693
|
};
|
|
@@ -6887,14 +6886,28 @@ function codexNativeGoalReplaceInstruction(mission, runtimeGoalState = null, obj
|
|
|
6887
6886
|
? ` from paused objective ${JSON.stringify(runtimeGoalState.objective)}`
|
|
6888
6887
|
: '';
|
|
6889
6888
|
if (options.allowNativeGoalSupersede === true) {
|
|
6890
|
-
return `
|
|
6889
|
+
return `Same-task supersede refused${fromObjective}. Create a new Codex task for ${JSON.stringify(objective)}, then run ${codexGoalAckCommand(mission, objective)} there.`;
|
|
6891
6890
|
}
|
|
6892
|
-
return `
|
|
6891
|
+
return `This Codex task keeps${fromObjective || ' its current objective'}. Create a new dedicated Codex task for ${JSON.stringify(objective)}, then run ${codexGoalAckCommand(mission, objective)} there.`;
|
|
6893
6892
|
}
|
|
6894
6893
|
|
|
6895
6894
|
function codexNativeGoalBlockPayload(mission, options = {}) {
|
|
6896
6895
|
const objective = codexGoalObjective(mission);
|
|
6897
6896
|
const runtimeGoalState = codexRuntimeGoalStateFromOptions(options);
|
|
6897
|
+
if (codexRuntimeTaskIsClosed(runtimeGoalState)) {
|
|
6898
|
+
return {
|
|
6899
|
+
ok: false,
|
|
6900
|
+
code: 'completed_task_closed',
|
|
6901
|
+
mission_id: mission.id,
|
|
6902
|
+
objective,
|
|
6903
|
+
runtime_goal_state: runtimeGoalState,
|
|
6904
|
+
requires_native_goal_start: false,
|
|
6905
|
+
requires_new_task: true,
|
|
6906
|
+
native_goal_action: null,
|
|
6907
|
+
native_goal_ack_command: null,
|
|
6908
|
+
next_action: 'Create a new Codex task for this mission. This completed task must retain its final state.',
|
|
6909
|
+
};
|
|
6910
|
+
}
|
|
6898
6911
|
const recovery = codexNativeGoalRecovery(mission, runtimeGoalState);
|
|
6899
6912
|
if (recovery) {
|
|
6900
6913
|
return {
|
|
@@ -6985,30 +6998,32 @@ function codexGoalReplaceAfterInstruction(mission) {
|
|
|
6985
6998
|
if (missionBudgetContinuationText(mission)) {
|
|
6986
6999
|
return 'After each proof, run atris mission goal --json again and keep the matching Codex /goal active until the full budget is used.';
|
|
6987
7000
|
}
|
|
6988
|
-
return 'After proof or verifier pass,
|
|
7001
|
+
return 'After proof or verifier pass, complete this Codex task and stop. A different objective belongs in a new Codex task.';
|
|
6989
7002
|
}
|
|
6990
7003
|
|
|
6991
7004
|
function codexVisibleGoalBridge(mission, goalObjective, options = {}) {
|
|
6992
7005
|
const ack = codexNativeGoalAck(mission, goalObjective);
|
|
6993
7006
|
const recovery = options.nativeGoalRecovery || null;
|
|
7007
|
+
const completedTaskClosed = options.completedTaskClosed === true;
|
|
6994
7008
|
return {
|
|
6995
7009
|
schema: 'atris.visible_chat_goal_bridge.v1',
|
|
6996
7010
|
runtime: 'codex',
|
|
6997
7011
|
source: 'atris_mission',
|
|
6998
7012
|
mission_id: mission.id,
|
|
6999
7013
|
desired_objective: goalObjective,
|
|
7000
|
-
status: ack ? 'active' : (recovery ? 'needs_ack_recovery' : 'needs_runtime_write'),
|
|
7014
|
+
status: completedTaskClosed ? 'completed_task_closed' : (ack ? 'active' : (recovery ? 'needs_ack_recovery' : 'needs_runtime_write')),
|
|
7001
7015
|
acknowledged_at: ack?.acknowledged_at || null,
|
|
7002
7016
|
state_file: '.atris/state/codex_goal.json',
|
|
7003
7017
|
status_file: 'atris/status/codex-goal.md',
|
|
7004
7018
|
operations: {
|
|
7005
7019
|
read_current_goal: 'get_goal',
|
|
7006
7020
|
keep_if_matching: 'if current goal objective equals goal.objective, continue the mission',
|
|
7007
|
-
|
|
7021
|
+
create_when_no_goal: ack || recovery || completedTaskClosed ? null : 'create_goal({ objective: goal.objective })',
|
|
7022
|
+
completed_task_action: completedTaskClosed ? 'stop this task and create a new dedicated Codex task for the next objective' : null,
|
|
7008
7023
|
ack_existing_matching_goal: recovery?.commands?.ack_current_goal || null,
|
|
7009
7024
|
handoff_when_usage_limited: recovery?.commands?.handoff_to_fresh_agent || null,
|
|
7010
|
-
ack_after_create: codexGoalAckCommand(mission, goalObjective),
|
|
7011
|
-
complete_after_proof: codexGoalCompletionInstruction(mission),
|
|
7025
|
+
ack_after_create: completedTaskClosed ? null : codexGoalAckCommand(mission, goalObjective),
|
|
7026
|
+
complete_after_proof: completedTaskClosed ? null : codexGoalCompletionInstruction(mission),
|
|
7012
7027
|
refresh_on_phase_change: 'atris mission goal --json before continuing changed work',
|
|
7013
7028
|
refresh_next_candidate: 'atris mission goal --json',
|
|
7014
7029
|
},
|
|
@@ -7016,6 +7031,7 @@ function codexVisibleGoalBridge(mission, goalObjective, options = {}) {
|
|
|
7016
7031
|
...(missionBudgetContinuationText(mission)
|
|
7017
7032
|
? ['Keep the matching native goal active until the full budget is used, even when an intermediate verifier passes.']
|
|
7018
7033
|
: []),
|
|
7034
|
+
'A completed Codex task stays closed; never create another goal in that task.',
|
|
7019
7035
|
'Do not complete a human-set active goal unless it matches this mission goal or the mission receipt proves handoff.',
|
|
7020
7036
|
'If create_goal fails because another goal is active, keep this bridge waiting for the visible goal slot.',
|
|
7021
7037
|
'Do not run mission work for runner=codex_goal until ack_after_create has been recorded.',
|
|
@@ -7024,7 +7040,23 @@ function codexVisibleGoalBridge(mission, goalObjective, options = {}) {
|
|
|
7024
7040
|
};
|
|
7025
7041
|
}
|
|
7026
7042
|
|
|
7027
|
-
function codexGoalToolContract(mission, nativeGoalRecovery = null) {
|
|
7043
|
+
function codexGoalToolContract(mission, nativeGoalRecovery = null, options = {}) {
|
|
7044
|
+
if (options.completedTaskClosed === true) {
|
|
7045
|
+
return {
|
|
7046
|
+
current_policy: 'completed Codex tasks retain their final goal state',
|
|
7047
|
+
read_current_goal: 'get_goal',
|
|
7048
|
+
complete_current_goal: 'already complete; do not mutate this task',
|
|
7049
|
+
select_next_goal: 'create a new Codex task for a different objective',
|
|
7050
|
+
set_next_goal: 'do not call create_goal in this completed task',
|
|
7051
|
+
visible_goal_bridge: 'goal.visible_goal',
|
|
7052
|
+
platform_requirement: 'New work and recurring monitors need their own Codex task.',
|
|
7053
|
+
phase_change_refresh: 'continue only while this task is active and the objective still matches',
|
|
7054
|
+
runtime_tool_sequence: 'get_goal -> if complete, stop this task -> create a new Codex task for the next objective',
|
|
7055
|
+
blocked_without_platform_goal_write: true,
|
|
7056
|
+
requires_new_task: true,
|
|
7057
|
+
mission_id: mission.id,
|
|
7058
|
+
};
|
|
7059
|
+
}
|
|
7028
7060
|
if (nativeGoalRecovery) {
|
|
7029
7061
|
return {
|
|
7030
7062
|
current_policy: 'keep one visible Codex /goal active for the selected Atris mission',
|
|
@@ -7045,11 +7077,11 @@ function codexGoalToolContract(mission, nativeGoalRecovery = null) {
|
|
|
7045
7077
|
read_current_goal: 'get_goal',
|
|
7046
7078
|
complete_current_goal: codexGoalCompletionInstruction(mission),
|
|
7047
7079
|
select_next_goal: 'atris mission goal --json',
|
|
7048
|
-
set_next_goal: 'use goal.visible_goal
|
|
7080
|
+
set_next_goal: 'use goal.visible_goal only when get_goal reports no goal for this task',
|
|
7049
7081
|
visible_goal_bridge: 'goal.visible_goal',
|
|
7050
|
-
platform_requirement: 'Codex runtime must
|
|
7051
|
-
phase_change_refresh: '
|
|
7052
|
-
runtime_tool_sequence: 'get_goal -> create_goal({ objective }) ->
|
|
7082
|
+
platform_requirement: 'Codex runtime must create a new task when a completed task needs a different objective.',
|
|
7083
|
+
phase_change_refresh: 'continue in this task only while its active objective still matches',
|
|
7084
|
+
runtime_tool_sequence: 'get_goal -> if no goal, create_goal({ objective }) -> acknowledge -> do matching work -> update_goal({ status: "complete" }) after proof -> stop this task',
|
|
7053
7085
|
blocked_without_platform_goal_write: true,
|
|
7054
7086
|
mission_id: mission.id,
|
|
7055
7087
|
};
|
|
@@ -7092,8 +7124,11 @@ function writeCodexGoalState(payload, root = process.cwd()) {
|
|
|
7092
7124
|
if (state.goal.visible_goal) {
|
|
7093
7125
|
lines.push(`- visible goal: ${state.goal.visible_goal.status}`);
|
|
7094
7126
|
lines.push(`- visible goal desired: ${state.goal.visible_goal.desired_objective}`);
|
|
7095
|
-
if (state.goal.visible_goal.operations.
|
|
7096
|
-
lines.push(`- visible goal create: ${state.goal.visible_goal.operations.
|
|
7127
|
+
if (state.goal.visible_goal.operations.create_when_no_goal) {
|
|
7128
|
+
lines.push(`- visible goal create: ${state.goal.visible_goal.operations.create_when_no_goal}`);
|
|
7129
|
+
}
|
|
7130
|
+
if (state.goal.visible_goal.operations.completed_task_action) {
|
|
7131
|
+
lines.push(`- completed task: ${state.goal.visible_goal.operations.completed_task_action}`);
|
|
7097
7132
|
}
|
|
7098
7133
|
if (state.goal.visible_goal.operations.ack_existing_matching_goal) {
|
|
7099
7134
|
lines.push(`- visible goal ack recovery: ${state.goal.visible_goal.operations.ack_existing_matching_goal}`);
|
|
@@ -7101,10 +7136,12 @@ function writeCodexGoalState(payload, root = process.cwd()) {
|
|
|
7101
7136
|
if (state.goal.visible_goal.operations.handoff_when_usage_limited) {
|
|
7102
7137
|
lines.push(`- usage-limited handoff: ${state.goal.visible_goal.operations.handoff_when_usage_limited}`);
|
|
7103
7138
|
}
|
|
7104
|
-
|
|
7105
|
-
|
|
7106
|
-
|
|
7107
|
-
|
|
7139
|
+
if (state.goal.visible_goal.operations.complete_after_proof) {
|
|
7140
|
+
const completionLabel = missionBudgetContinuationText(state.mission)
|
|
7141
|
+
? 'visible goal hold'
|
|
7142
|
+
: 'visible goal complete';
|
|
7143
|
+
lines.push(`- ${completionLabel}: ${state.goal.visible_goal.operations.complete_after_proof}`);
|
|
7144
|
+
}
|
|
7108
7145
|
}
|
|
7109
7146
|
if (state.goal.native_goal_recovery) {
|
|
7110
7147
|
lines.push(`- native goal recovery: ${state.goal.native_goal_recovery.next_command}`);
|
|
@@ -7174,15 +7211,18 @@ function buildCodexGoalPayload(root = process.cwd(), options = {}) {
|
|
|
7174
7211
|
|
|
7175
7212
|
let { mission } = selected;
|
|
7176
7213
|
const { reason, direct_goal_request: directGoalRequest, seeded_continuation_goal: seededContinuationGoal } = selected;
|
|
7177
|
-
const
|
|
7214
|
+
const completedTaskClosed = codexRuntimeTaskIsClosed(runtimeGoalState);
|
|
7215
|
+
const autoNativeGoalAck = completedTaskClosed ? null : maybeAutoAckCodexNativeGoal(mission, root, options);
|
|
7178
7216
|
if (autoNativeGoalAck) mission = autoNativeGoalAck.saved;
|
|
7179
7217
|
const taskSpine = missionTaskSpine(mission);
|
|
7180
7218
|
const missionView = missionStatusView(mission);
|
|
7181
7219
|
const objective = codexGoalObjective(mission);
|
|
7182
7220
|
const ack = codexNativeGoalAck(mission);
|
|
7183
|
-
const nativeGoalRecovery = !ack ? codexNativeGoalRecovery(mission, runtimeGoalState, root) : null;
|
|
7184
|
-
const runtimeNeedsReplace = !ack && !nativeGoalRecovery && codexRuntimeGoalNeedsReplace(runtimeGoalState, objective);
|
|
7185
|
-
const nativeGoalAction =
|
|
7221
|
+
const nativeGoalRecovery = !completedTaskClosed && !ack ? codexNativeGoalRecovery(mission, runtimeGoalState, root) : null;
|
|
7222
|
+
const runtimeNeedsReplace = !completedTaskClosed && !ack && !nativeGoalRecovery && codexRuntimeGoalNeedsReplace(runtimeGoalState, objective);
|
|
7223
|
+
const nativeGoalAction = completedTaskClosed
|
|
7224
|
+
? null
|
|
7225
|
+
: ack
|
|
7186
7226
|
? null
|
|
7187
7227
|
: nativeGoalRecovery
|
|
7188
7228
|
? null
|
|
@@ -7201,7 +7241,9 @@ function buildCodexGoalPayload(root = process.cwd(), options = {}) {
|
|
|
7201
7241
|
executed_by: taskSpine?.executed_by || mission.executed_by || null,
|
|
7202
7242
|
task_spine: taskSpine,
|
|
7203
7243
|
reason,
|
|
7204
|
-
next_command:
|
|
7244
|
+
next_command: completedTaskClosed
|
|
7245
|
+
? 'Create a new Codex task for this mission. This completed task must retain its final state.'
|
|
7246
|
+
: nativeGoalRecovery
|
|
7205
7247
|
? nativeGoalRecovery.next_command
|
|
7206
7248
|
: runtimeNeedsReplace
|
|
7207
7249
|
? codexNativeGoalReplaceInstruction(mission, runtimeGoalState, objective, {
|
|
@@ -7209,11 +7251,12 @@ function buildCodexGoalPayload(root = process.cwd(), options = {}) {
|
|
|
7209
7251
|
})
|
|
7210
7252
|
: codexGoalNextCommand(mission),
|
|
7211
7253
|
replace_after: codexGoalReplaceAfterInstruction(mission),
|
|
7212
|
-
visible_goal: codexVisibleGoalBridge(mission, objective, { nativeGoalRecovery }),
|
|
7213
|
-
codex_tool_contract: codexGoalToolContract(mission, nativeGoalRecovery),
|
|
7214
|
-
requires_native_goal_start: !ack && !nativeGoalRecovery,
|
|
7254
|
+
visible_goal: codexVisibleGoalBridge(mission, objective, { nativeGoalRecovery, completedTaskClosed }),
|
|
7255
|
+
codex_tool_contract: codexGoalToolContract(mission, nativeGoalRecovery, { completedTaskClosed }),
|
|
7256
|
+
requires_native_goal_start: !completedTaskClosed && !ack && !nativeGoalRecovery,
|
|
7215
7257
|
requires_native_goal_recovery: Boolean(nativeGoalRecovery),
|
|
7216
7258
|
requires_native_goal_replace: runtimeNeedsReplace,
|
|
7259
|
+
requires_new_task: completedTaskClosed,
|
|
7217
7260
|
native_goal_action: nativeGoalAction,
|
|
7218
7261
|
native_goal_recovery: nativeGoalRecovery,
|
|
7219
7262
|
native_goal_ack_command: codexGoalAckCommand(mission, objective),
|
|
@@ -7230,13 +7273,14 @@ function buildCodexGoalPayload(root = process.cwd(), options = {}) {
|
|
|
7230
7273
|
const heartbeat = heartbeatMode ? codexGoalHeartbeat(goal, mission) : undefined;
|
|
7231
7274
|
return {
|
|
7232
7275
|
ok: true,
|
|
7233
|
-
action: heartbeatMode ? 'codex_goal_heartbeat' : 'codex_goal_candidate',
|
|
7276
|
+
action: completedTaskClosed ? 'completed_task_closed' : (heartbeatMode ? 'codex_goal_heartbeat' : 'codex_goal_candidate'),
|
|
7234
7277
|
goal,
|
|
7235
7278
|
mission: missionView,
|
|
7236
7279
|
heartbeat,
|
|
7237
7280
|
requires_native_goal_start: goal.requires_native_goal_start,
|
|
7238
7281
|
requires_native_goal_recovery: goal.requires_native_goal_recovery,
|
|
7239
7282
|
requires_native_goal_replace: goal.requires_native_goal_replace,
|
|
7283
|
+
requires_new_task: goal.requires_new_task,
|
|
7240
7284
|
native_goal_action: goal.native_goal_action,
|
|
7241
7285
|
native_goal_recovery: goal.native_goal_recovery,
|
|
7242
7286
|
auto_native_goal_ack: goal.auto_native_goal_ack,
|
|
@@ -7453,6 +7497,7 @@ function goalLoopNextCommandPlan(goal) {
|
|
|
7453
7497
|
|
|
7454
7498
|
function shouldRunGoalLoopCommand(heartbeat, plan) {
|
|
7455
7499
|
if (!heartbeat?.goal) return false;
|
|
7500
|
+
if (heartbeat.action === 'completed_task_closed' || heartbeat.goal.requires_new_task === true) return false;
|
|
7456
7501
|
if (heartbeat.goal.requires_native_goal_start === true) return false;
|
|
7457
7502
|
if (plan && plan.run_when_due_only === false) return true;
|
|
7458
7503
|
return heartbeat.heartbeat?.due === true;
|
|
@@ -10409,12 +10454,12 @@ function reapPausedMissions(root = process.cwd(), { hours = MISSION_PAUSED_REAP_
|
|
|
10409
10454
|
}
|
|
10410
10455
|
|
|
10411
10456
|
function missionGoalHelp() {
|
|
10412
|
-
console.log('Usage: atris mission goal [--runtime codex|atris] [--heartbeat] [--native-goal-status active|paused|usageLimited] [--native-goal-objective "..."] [--manual-ack] [--allow-native-goal-supersede] [--json]');
|
|
10457
|
+
console.log('Usage: atris mission goal [--runtime codex|atris] [--heartbeat] [--native-goal-status active|paused|usageLimited|complete] [--native-goal-objective "..."] [--manual-ack] [--allow-native-goal-supersede] [--json]');
|
|
10413
10458
|
console.log('Refresh the visible native goal from active mission state. Help is read-only.');
|
|
10414
10459
|
}
|
|
10415
10460
|
|
|
10416
10461
|
function missionGoalLoopHelp() {
|
|
10417
|
-
console.log('Usage: atris mission goal-loop [--max-wall 28800] [--max-iterations 32] [--no-claude] [--dry-run] [--once] [--json]');
|
|
10462
|
+
console.log('Usage: atris mission goal-loop [--max-wall 28800] [--max-iterations 32] [--no-claude] [--native-goal-status active|paused|usageLimited|complete] [--native-goal-objective "..."] [--dry-run] [--once] [--json]');
|
|
10418
10463
|
console.log('Run the bounded native-goal controller. Help is read-only and never starts due work.');
|
|
10419
10464
|
}
|
|
10420
10465
|
|
|
@@ -10470,6 +10515,18 @@ function goalMission(args) {
|
|
|
10470
10515
|
);
|
|
10471
10516
|
return;
|
|
10472
10517
|
}
|
|
10518
|
+
if (payload.action === 'completed_task_closed') {
|
|
10519
|
+
printJsonOrText(
|
|
10520
|
+
payload,
|
|
10521
|
+
[
|
|
10522
|
+
'Completed Codex task stays closed.',
|
|
10523
|
+
`Next: ${payload.goal.next_command}`,
|
|
10524
|
+
],
|
|
10525
|
+
asJson,
|
|
10526
|
+
);
|
|
10527
|
+
process.exitCode = 2;
|
|
10528
|
+
return;
|
|
10529
|
+
}
|
|
10473
10530
|
|
|
10474
10531
|
printJsonOrText(
|
|
10475
10532
|
payload,
|
|
@@ -10566,12 +10623,13 @@ async function goalLoopMission(args) {
|
|
|
10566
10623
|
const once = hasFlag(args, '--once');
|
|
10567
10624
|
const maxIterations = once ? 1 : Math.max(1, Number(readFlag(args, '--max-iterations', '')) || 32);
|
|
10568
10625
|
const maxWallSeconds = Math.max(1, Number(readFlag(args, '--max-wall', '')) || 8 * 60 * 60);
|
|
10626
|
+
const nativeGoalOptions = codexNativeGoalOptionsFromArgs(args);
|
|
10569
10627
|
const root = process.cwd();
|
|
10570
10628
|
const startedAt = Date.now();
|
|
10571
10629
|
const events = [];
|
|
10572
10630
|
|
|
10573
10631
|
for (let index = 0; index < maxIterations; index += 1) {
|
|
10574
|
-
const heartbeat = refreshCodexGoalController(root, { heartbeat: true });
|
|
10632
|
+
const heartbeat = refreshCodexGoalController(root, { heartbeat: true, ...nativeGoalOptions });
|
|
10575
10633
|
const event = {
|
|
10576
10634
|
iteration: index + 1,
|
|
10577
10635
|
heartbeat,
|
|
@@ -10597,11 +10655,12 @@ async function goalLoopMission(args) {
|
|
|
10597
10655
|
event.run = runMissionGoalNextCommand(root, heartbeat, { noClaude });
|
|
10598
10656
|
event.ran_heavy_work = event.run.ok === true && event.run.heavy_work === true;
|
|
10599
10657
|
event.ran_setup_work = event.run.ok === true && event.run.setup_work === true;
|
|
10600
|
-
event.after_run = refreshCodexGoalController(root, { heartbeat: true });
|
|
10658
|
+
event.after_run = refreshCodexGoalController(root, { heartbeat: true, ...nativeGoalOptions });
|
|
10601
10659
|
}
|
|
10602
10660
|
}
|
|
10603
10661
|
events.push(event);
|
|
10604
10662
|
|
|
10663
|
+
if (heartbeat.action === 'completed_task_closed') break;
|
|
10605
10664
|
if (index + 1 >= maxIterations) break;
|
|
10606
10665
|
const elapsedSeconds = (Date.now() - startedAt) / 1000;
|
|
10607
10666
|
const remainingSeconds = maxWallSeconds - elapsedSeconds;
|
|
@@ -10615,9 +10674,10 @@ async function goalLoopMission(args) {
|
|
|
10615
10674
|
await sleep(sleepSeconds * 1000);
|
|
10616
10675
|
}
|
|
10617
10676
|
|
|
10618
|
-
const finalState = refreshCodexGoalController(root, { heartbeat: true });
|
|
10677
|
+
const finalState = refreshCodexGoalController(root, { heartbeat: true, ...nativeGoalOptions });
|
|
10678
|
+
const completedTaskClosed = finalState.action === 'completed_task_closed';
|
|
10619
10679
|
const payload = {
|
|
10620
|
-
ok:
|
|
10680
|
+
ok: !completedTaskClosed,
|
|
10621
10681
|
action: 'codex_goal_loop',
|
|
10622
10682
|
iterations: events.length,
|
|
10623
10683
|
max_iterations: maxIterations,
|
|
@@ -10626,6 +10686,8 @@ async function goalLoopMission(args) {
|
|
|
10626
10686
|
setup_runs: events.filter((event) => event.ran_setup_work).length,
|
|
10627
10687
|
events,
|
|
10628
10688
|
final_state: finalState,
|
|
10689
|
+
status: completedTaskClosed ? 'completed_task_closed' : 'running',
|
|
10690
|
+
next_action: completedTaskClosed ? finalState.goal?.next_command || null : null,
|
|
10629
10691
|
};
|
|
10630
10692
|
printJsonOrText(
|
|
10631
10693
|
payload,
|
|
@@ -10637,6 +10699,7 @@ async function goalLoopMission(args) {
|
|
|
10637
10699
|
],
|
|
10638
10700
|
asJson,
|
|
10639
10701
|
);
|
|
10702
|
+
if (completedTaskClosed) process.exitCode = 2;
|
|
10640
10703
|
}
|
|
10641
10704
|
|
|
10642
10705
|
function help() {
|
|
@@ -10662,9 +10725,9 @@ atris mission - durable goal + loop + owner + proof state
|
|
|
10662
10725
|
(rolls up sibling git-worktree missions; --local scopes to this checkout)
|
|
10663
10726
|
atris mission room "<messy input>" [--owner <member>] [--room-auto-run] [--json] Create a Mission Room card and shareable receipt from messy intent
|
|
10664
10727
|
atris mission prune-runs [--apply] [--days <n>] [--keep-newest <n>] [--json] Compress old run receipts into a manifest and prune unreferenced clutter
|
|
10665
|
-
atris mission goal [--runtime codex|atris] [--heartbeat] [--native-goal-status active|paused|usageLimited] [--native-goal-objective "..."] [--manual-ack] [--allow-native-goal-supersede] [--json]
|
|
10728
|
+
atris mission goal [--runtime codex|atris] [--heartbeat] [--native-goal-status active|paused|usageLimited|complete] [--native-goal-objective "..."] [--manual-ack] [--allow-native-goal-supersede] [--json]
|
|
10666
10729
|
atris mission goal ack <id> --runtime codex --status active --objective "<objective>" --json
|
|
10667
|
-
atris mission goal-loop [--max-wall 28800] [--max-iterations 32] [--no-claude] [--json]
|
|
10730
|
+
atris mission goal-loop [--max-wall 28800] [--max-iterations 32] [--no-claude] [--native-goal-status active|paused|usageLimited|complete] [--native-goal-objective "..."] [--dry-run] [--once] [--json]
|
|
10668
10731
|
atris mission tick <id> [--verify ["cmd"]] [--complete-on-pass] [--self-drive] [--summary "..."]
|
|
10669
10732
|
[--native-goal-status active|paused|usageLimited] [--native-goal-objective "..."] [--json]
|
|
10670
10733
|
atris mission set-runner <id> <runner|engine> [--model <id>] [--json]
|
package/commands/pack.js
CHANGED
|
@@ -6,6 +6,7 @@ const path = require('path');
|
|
|
6
6
|
const { isUtf8 } = require('buffer');
|
|
7
7
|
const { createHash } = require('crypto');
|
|
8
8
|
const { spawnSync } = require('child_process');
|
|
9
|
+
const { version: CLI_VERSION } = require('../package.json');
|
|
9
10
|
const { apiRequestJson, getApiBaseUrl, getAppBaseUrl, httpRequest } = require('../utils/api');
|
|
10
11
|
const { loadCredentials, performTokenRefresh } = require('../utils/auth');
|
|
11
12
|
const { createZipBuffer, readZipBuffer, ZIP_LIMITS } = require('../lib/zip');
|
|
@@ -156,6 +157,7 @@ const ENV_ASSIGNMENT = /=(?!=*$)/;
|
|
|
156
157
|
|
|
157
158
|
function showHelp() {
|
|
158
159
|
console.log('usage: atris pack craft "<topic>" [--dir <target>] [--force]');
|
|
160
|
+
console.log(' atris pack seal <dir> [--type <t>] [--entrypoint <file>]');
|
|
159
161
|
console.log(' atris pack publish [--dir atris] [--slug <slug>] [--author "<name>"] [--notes "..."] [--visibility public|unlisted|private] [--minor|--major] [--out <file.zip>] [--push] [--dry-run] [--allow-secrets]');
|
|
160
162
|
console.log(' atris pack install <file.zip|url|slug> [--dir <target>] [--force]');
|
|
161
163
|
console.log(' atris pack run <slug|dir> [--dir <target>] [--input <file>] [--cloud] [--force] [--trust] [--grant <capability>]');
|
|
@@ -1377,14 +1379,8 @@ async function showPackSales(rawArgs, cwd = process.cwd(), options = {}) {
|
|
|
1377
1379
|
// ── pack purchases ──────────────────────────────────────────────────────────
|
|
1378
1380
|
const PACK_PURCHASES_LOGIN_NUDGE = 'not logged in. run atris login first to view pack purchases.';
|
|
1379
1381
|
|
|
1380
|
-
function
|
|
1381
|
-
|
|
1382
|
-
if (payload && Array.isArray(payload.purchases)) return payload.purchases;
|
|
1383
|
-
if (payload && Array.isArray(payload.data)) return payload.data;
|
|
1384
|
-
if (payload && payload.data && Array.isArray(payload.data.purchases)) {
|
|
1385
|
-
return payload.data.purchases;
|
|
1386
|
-
}
|
|
1387
|
-
return null;
|
|
1382
|
+
function packPurchasesUrl(apiBaseUrl = getApiBaseUrl()) {
|
|
1383
|
+
return `${String(apiBaseUrl || '').replace(/\/+$/, '')}/pack/purchases/mine`;
|
|
1388
1384
|
}
|
|
1389
1385
|
|
|
1390
1386
|
async function showPackPurchases(rawArgs, cwd = process.cwd(), options = {}) {
|
|
@@ -1393,28 +1389,32 @@ async function showPackPurchases(rawArgs, cwd = process.cwd(), options = {}) {
|
|
|
1393
1389
|
|
|
1394
1390
|
const deps = options.deps || {};
|
|
1395
1391
|
const print = options.print || console.log;
|
|
1392
|
+
const request = deps.httpRequest || httpRequest;
|
|
1393
|
+
const authHeaders = requiredAuthHeaders(deps, 'view pack purchases');
|
|
1396
1394
|
|
|
1397
|
-
let
|
|
1395
|
+
let response;
|
|
1398
1396
|
try {
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1397
|
+
const apiBaseUrl = (deps.getApiBaseUrl || getApiBaseUrl)();
|
|
1398
|
+
response = await request(packPurchasesUrl(apiBaseUrl), {
|
|
1399
|
+
method: 'GET',
|
|
1400
|
+
timeoutMs: REGISTRY_TIMEOUT_MS,
|
|
1401
|
+
headers: {
|
|
1402
|
+
Accept: 'application/json',
|
|
1403
|
+
...authHeaders,
|
|
1405
1404
|
},
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
throw
|
|
1405
|
+
});
|
|
1406
|
+
} catch {
|
|
1407
|
+
throw new Error('could not load pack purchases. check your connection and try again.');
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
if (response.status === 401) throw new Error(PACK_PURCHASES_LOGIN_NUDGE);
|
|
1411
|
+
if (response.status < 200 || response.status >= 300) {
|
|
1412
|
+
throw new Error(`could not load pack purchases (status ${response.status}).`);
|
|
1414
1413
|
}
|
|
1415
1414
|
|
|
1416
|
-
const
|
|
1417
|
-
if (!
|
|
1415
|
+
const parsed = parseJsonBody(response.body);
|
|
1416
|
+
if (!Array.isArray(parsed.data)) throw new Error('pack purchases returned an invalid response.');
|
|
1417
|
+
const purchases = parsed.data;
|
|
1418
1418
|
if (!purchases.length) {
|
|
1419
1419
|
print('no purchased packs yet. browse with: atris pack browse');
|
|
1420
1420
|
return 0;
|
|
@@ -3003,6 +3003,70 @@ function inspectInstalledContentHashes(packDir, manifest) {
|
|
|
3003
3003
|
};
|
|
3004
3004
|
}
|
|
3005
3005
|
|
|
3006
|
+
function sealPack(rawArgs, cwd = process.cwd()) {
|
|
3007
|
+
const args = [...rawArgs];
|
|
3008
|
+
const requestedType = takeValue(args, '--type');
|
|
3009
|
+
const requestedEntrypoint = takeValue(args, '--entrypoint');
|
|
3010
|
+
const source = args.shift();
|
|
3011
|
+
if (!source) {
|
|
3012
|
+
showHelp();
|
|
3013
|
+
return 2;
|
|
3014
|
+
}
|
|
3015
|
+
if (args.length) throw new Error(`unknown pack seal argument: ${args.join(' ')}`);
|
|
3016
|
+
|
|
3017
|
+
const packDir = path.resolve(cwd, source);
|
|
3018
|
+
const manifest = assertPacketDir(packDir, cwd);
|
|
3019
|
+
const contentFiles = collectInstalledContentFiles(packDir);
|
|
3020
|
+
const setFields = [];
|
|
3021
|
+
|
|
3022
|
+
if (!Object.prototype.hasOwnProperty.call(manifest, 'type')) {
|
|
3023
|
+
manifest.type = requestedType !== null
|
|
3024
|
+
? requestedType
|
|
3025
|
+
: (contentFiles.size === 1 && contentFiles.has('README.md') ? 'playbook' : 'context');
|
|
3026
|
+
setFields.push(['type', manifest.type]);
|
|
3027
|
+
}
|
|
3028
|
+
if (!Object.prototype.hasOwnProperty.call(manifest, 'entrypoint')) {
|
|
3029
|
+
const entrypoint = requestedEntrypoint !== null
|
|
3030
|
+
? requestedEntrypoint
|
|
3031
|
+
: (contentFiles.has('README.md') ? 'README.md' : null);
|
|
3032
|
+
if (entrypoint !== null) {
|
|
3033
|
+
manifest.entrypoint = entrypoint;
|
|
3034
|
+
setFields.push(['entrypoint', manifest.entrypoint]);
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
if (!Object.prototype.hasOwnProperty.call(manifest, 'permissions')) {
|
|
3038
|
+
manifest.permissions = [];
|
|
3039
|
+
setFields.push(['permissions', '[]']);
|
|
3040
|
+
}
|
|
3041
|
+
if (!Object.prototype.hasOwnProperty.call(manifest, 'created-in')) {
|
|
3042
|
+
manifest['created-in'] = CLI_VERSION;
|
|
3043
|
+
setFields.push(['created-in', manifest['created-in']]);
|
|
3044
|
+
}
|
|
3045
|
+
|
|
3046
|
+
manifest['content-hashes'] = Object.fromEntries(
|
|
3047
|
+
[...contentFiles.entries()]
|
|
3048
|
+
.map(([relativePath, absolutePath]) => {
|
|
3049
|
+
const contentPath = canonicalContentPath(relativePath);
|
|
3050
|
+
if (contentPath !== relativePath) {
|
|
3051
|
+
throw new Error(`pack content-hashes requires canonical file path: ${relativePath}`);
|
|
3052
|
+
}
|
|
3053
|
+
return [contentPath, sha256(fs.readFileSync(absolutePath))];
|
|
3054
|
+
})
|
|
3055
|
+
.sort(([left], [right]) => left.localeCompare(right)),
|
|
3056
|
+
);
|
|
3057
|
+
setFields.push([
|
|
3058
|
+
'content-hashes',
|
|
3059
|
+
`${contentFiles.size} file${contentFiles.size === 1 ? '' : 's'}`,
|
|
3060
|
+
]);
|
|
3061
|
+
|
|
3062
|
+
writeJson(path.join(packDir, 'pack.json'), manifest);
|
|
3063
|
+
for (const [field, value] of setFields) console.log(`set ${field}: ${value}`);
|
|
3064
|
+
|
|
3065
|
+
const result = evaluatePackDoctor(packDir, cwd);
|
|
3066
|
+
printPackDoctor(result);
|
|
3067
|
+
return result.ok ? 0 : 1;
|
|
3068
|
+
}
|
|
3069
|
+
|
|
3006
3070
|
function printContentHashStatus(result) {
|
|
3007
3071
|
if (result.status === 'absent') {
|
|
3008
3072
|
console.log(' content hashes: absent (legacy pack, bytes unverified)');
|
|
@@ -3942,6 +4006,7 @@ async function run(argv = []) {
|
|
|
3942
4006
|
}
|
|
3943
4007
|
return result;
|
|
3944
4008
|
}
|
|
4009
|
+
if (subcommand === 'seal') return sealPack(args);
|
|
3945
4010
|
if (subcommand === 'publish') return await publishPack(args);
|
|
3946
4011
|
if (subcommand === 'install') return await installPack(args);
|
|
3947
4012
|
if (subcommand === 'run') return await runPack(args);
|
package/commands/sync.js
CHANGED
|
@@ -3,6 +3,12 @@ const path = require('path');
|
|
|
3
3
|
const os = require('os');
|
|
4
4
|
const { ensureWikiScaffold } = require('../lib/wiki');
|
|
5
5
|
const { upsertAtrisClaudeBootBlock } = require('../lib/claude-boot-block');
|
|
6
|
+
const {
|
|
7
|
+
upsertAgentVoiceCard,
|
|
8
|
+
upsertClaudeVoiceHook,
|
|
9
|
+
upsertCursorVoiceCard,
|
|
10
|
+
voiceCardForRoot,
|
|
11
|
+
} = require('../lib/voice-card');
|
|
6
12
|
|
|
7
13
|
const TEMPLATE_ROOT_DIR = path.join(__dirname, '..', 'templates');
|
|
8
14
|
const WORKSPACE_TEMPLATES = {
|
|
@@ -251,6 +257,8 @@ function renderBusinessAgentAdapter(bizMeta = {}, targetRoot = '.') {
|
|
|
251
257
|
'',
|
|
252
258
|
'- Check `atris/MAP.md` before broad code or file search.',
|
|
253
259
|
'- Use `atris task` for ownership, notes, proof, and review state.',
|
|
260
|
+
'- Give every task a plain first layer: what changes, why it matters, and what done looks like. Keep all technical detail underneath unchanged.',
|
|
261
|
+
'- Use the existing Plan/Do and accept/revise gates for approve or change actions; never bypass proof.',
|
|
254
262
|
'- Use `atris mission` when work should survive the current chat.',
|
|
255
263
|
'- Put completed agent work in Review with `atris task ready <id> --proof "<receipt>" --result "<day-one PM sentence>".`',
|
|
256
264
|
'- Do not run `atris task accept` or claim XP unless a human approved the proof.',
|
|
@@ -553,6 +561,21 @@ function syncAtris(options = {}) {
|
|
|
553
561
|
// Sync all skills from package to user's project via shared helper.
|
|
554
562
|
updated += syncPackageSkills(targetDir, { verbose: true, dryRun });
|
|
555
563
|
|
|
564
|
+
const voiceCard = voiceCardForRoot(process.cwd());
|
|
565
|
+
const cursorVoiceFile = path.join(process.cwd(), '.cursor', 'rules', 'atris-voice.mdc');
|
|
566
|
+
const cursorVoiceResult = upsertCursorVoiceCard(cursorVoiceFile, voiceCard, { dryRun });
|
|
567
|
+
if (cursorVoiceResult.action !== 'unchanged') {
|
|
568
|
+
console.log(`${dryRun ? 'Would update' : '✓ Updated'} .cursor/rules/atris-voice.mdc`);
|
|
569
|
+
updated++;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const agentsMdFile = path.join(process.cwd(), 'AGENTS.md');
|
|
573
|
+
const agentsVoiceResult = upsertAgentVoiceCard(agentsMdFile, voiceCard, { dryRun });
|
|
574
|
+
if (agentsVoiceResult.action !== 'unchanged') {
|
|
575
|
+
console.log(`${dryRun ? 'Would update' : '✓ Updated'} AGENTS.md voice card`);
|
|
576
|
+
updated++;
|
|
577
|
+
}
|
|
578
|
+
|
|
556
579
|
// Update .claude/skills/atris/SKILL.md (legacy - now handled above, keeping for compatibility)
|
|
557
580
|
const claudeSkillsDir = path.join(process.cwd(), '.claude', 'skills', 'atris');
|
|
558
581
|
const claudeSkillFile = path.join(claudeSkillsDir, 'SKILL.md');
|
|
@@ -584,6 +607,9 @@ Key behaviors:
|
|
|
584
607
|
- Read PERSONA.md (3-4 sentences, ASCII visuals)
|
|
585
608
|
- Check MAP.md for file:line refs
|
|
586
609
|
- Use \`atris task\` for claims, proof, ready, and accept
|
|
610
|
+
- Give every task a plain first layer: what changes, why it matters, and what
|
|
611
|
+
done looks like. Keep exact technical detail underneath and use the existing
|
|
612
|
+
approve/change gates; never bypass proof.
|
|
587
613
|
- Treat TODO.md as a rendered view; regenerate it instead of hand-editing tasks`;
|
|
588
614
|
|
|
589
615
|
if (!dryRun && !fs.existsSync(claudeSkillsDir)) {
|
|
@@ -596,10 +622,11 @@ Key behaviors:
|
|
|
596
622
|
updated++;
|
|
597
623
|
}
|
|
598
624
|
|
|
599
|
-
// Update .claude/settings.json with
|
|
625
|
+
// Update .claude/settings.json with startup and per-prompt hooks.
|
|
600
626
|
const claudeSettingsFile = path.join(process.cwd(), '.claude', 'settings.json');
|
|
601
|
-
|
|
602
|
-
|
|
627
|
+
const claudeSettingsResult = upsertClaudeVoiceHook(claudeSettingsFile, {
|
|
628
|
+
dryRun,
|
|
629
|
+
initialSettings: {
|
|
603
630
|
hooks: {
|
|
604
631
|
SessionStart: [
|
|
605
632
|
{
|
|
@@ -612,9 +639,10 @@ Key behaviors:
|
|
|
612
639
|
}
|
|
613
640
|
]
|
|
614
641
|
}
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
|
|
642
|
+
},
|
|
643
|
+
});
|
|
644
|
+
if (claudeSettingsResult.action !== 'unchanged' && claudeSettingsResult.action !== 'skipped') {
|
|
645
|
+
console.log(`${dryRun ? 'Would update' : '✓ Updated'} .claude/settings.json (voice hook)`);
|
|
618
646
|
updated++;
|
|
619
647
|
}
|
|
620
648
|
|