orizu 0.5.5 → 0.5.7
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/dist/hosted-bootstrap.js +6 -1
- package/dist/hosted-runtime-assets.js +8 -0
- package/dist/hosted-session-cli.js +2 -2
- package/dist/manifests-cli.js +78 -2
- package/package.json +1 -1
- package/src/hosted-bootstrap.ts +6 -0
- package/src/hosted-runtime-assets.ts +9 -0
- package/src/hosted-session-cli.ts +2 -2
- package/src/manifests-cli.ts +83 -2
package/dist/hosted-bootstrap.js
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
*/
|
|
25
25
|
import { sweepForTokenResidue } from './daytona-slice.js';
|
|
26
26
|
import { redactSecrets } from './secret-redaction.js';
|
|
27
|
-
import { BEARER_BASENAME, BOOT_CONTEXT_BASENAME, DEFAULT_CACHE_REFRESH_BUFFER_MS, HELPER_SCRIPT_BASENAME, PREBAKED_MARKER_PATH, REPO_CRED_CACHE_BASENAME, SETUP_HOOK_RELATIVE_PATH, parsePrebakedMarker, renderCredentialHelperScript, serializeBootContext, } from './hosted-runtime-assets.js';
|
|
27
|
+
import { AGENT_GIT_IDENTITY, BEARER_BASENAME, BOOT_CONTEXT_BASENAME, DEFAULT_CACHE_REFRESH_BUFFER_MS, HELPER_SCRIPT_BASENAME, PREBAKED_MARKER_PATH, REPO_CRED_CACHE_BASENAME, SETUP_HOOK_RELATIVE_PATH, parsePrebakedMarker, renderCredentialHelperScript, serializeBootContext, } from './hosted-runtime-assets.js';
|
|
28
28
|
function newEventId() {
|
|
29
29
|
const cryptoObj = globalThis.crypto;
|
|
30
30
|
if (cryptoObj?.randomUUID)
|
|
@@ -236,6 +236,11 @@ export async function bootstrapHostedSandbox(opts) {
|
|
|
236
236
|
// Persist the helper repo-LOCAL (never --global) for subsequent fetch/push.
|
|
237
237
|
await session.exec(`git -C ${workspaceDir} config credential.helper ${shellSingleQuote(helperValue)}`);
|
|
238
238
|
await session.exec(`git -C ${workspaceDir} config credential.useHttpPath true`);
|
|
239
|
+
// Repo-LOCAL agent identity: the sandbox has no ambient git identity, so
|
|
240
|
+
// without this every agent `git commit` dies with "Author identity unknown"
|
|
241
|
+
// (ALI-1036, found live in QA-3). Same identity the loop stamps on events.
|
|
242
|
+
await session.exec(`git -C ${workspaceDir} config user.name ${shellSingleQuote(AGENT_GIT_IDENTITY.name)}`);
|
|
243
|
+
await session.exec(`git -C ${workspaceDir} config user.email ${shellSingleQuote(AGENT_GIT_IDENTITY.email)}`);
|
|
239
244
|
record('repo_cloned', true, `cloned ${opts.sessionBranch}`);
|
|
240
245
|
await sink.append('repo_cloned', { branch: opts.sessionBranch, workspaceDir });
|
|
241
246
|
// 4 — Provision the Orizu CLI (non-fatal, recorded — OpenInspect setup.sh
|
|
@@ -31,6 +31,14 @@ export const DEFAULT_CACHE_REFRESH_BUFFER_MS = 5 * 60 * 1000;
|
|
|
31
31
|
/** Customer setup hook path, relative to the repo root (mirrors OpenInspect's
|
|
32
32
|
* `.openinspect/setup.sh` — fresh-boot only, non-fatal, output captured). */
|
|
33
33
|
export const SETUP_HOOK_RELATIVE_PATH = '.orizu/setup.sh';
|
|
34
|
+
/**
|
|
35
|
+
* The git author identity for hosted-agent commits. Bootstrap configures it
|
|
36
|
+
* repo-LOCAL in the sandbox clone (never --global) so agent `git commit` works
|
|
37
|
+
* out of the box — the sandbox has no ambient identity and commits died with
|
|
38
|
+
* "Author identity unknown" (ALI-1036, found live in QA-3). The loop uses the
|
|
39
|
+
* same identity for run-event attribution; keep them identical.
|
|
40
|
+
*/
|
|
41
|
+
export const AGENT_GIT_IDENTITY = { name: 'Orizu Agent', email: 'agent@orizu.ai' };
|
|
34
42
|
// -- Pre-baked runtime marker (ALI-1017) -------------------------------------
|
|
35
43
|
/**
|
|
36
44
|
* Absolute path of the marker the pre-baked hosted-runtime image writes at build
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
import { createLocalSimProvider, } from './sandbox-provider.js';
|
|
38
38
|
import { bootstrapHostedSandbox, createBootstrapRunEventSink, } from './hosted-bootstrap.js';
|
|
39
39
|
import { resumeRunEventSink } from './hosted-run-event-sink.js';
|
|
40
|
-
import { BEARER_BASENAME } from './hosted-runtime-assets.js';
|
|
40
|
+
import { AGENT_GIT_IDENTITY, BEARER_BASENAME } from './hosted-runtime-assets.js';
|
|
41
41
|
import { createVercelProvider } from './vercel-sandbox-provider.js';
|
|
42
42
|
import { buildEgressPolicy, DEFAULT_EGRESS_CANARY_HOST } from './egress-policy.js';
|
|
43
43
|
import { hostedLoopCommand } from './hosted-loop.js';
|
|
@@ -355,7 +355,7 @@ export async function startHostedSession(opts) {
|
|
|
355
355
|
model,
|
|
356
356
|
reasoningEffort: opts.reasoningEffort,
|
|
357
357
|
messageId: `${runId}:task`,
|
|
358
|
-
author:
|
|
358
|
+
author: AGENT_GIT_IDENTITY,
|
|
359
359
|
anthropicDummyKey: opts.modelApiKey ? ANTHROPIC_DUMMY_KEY : undefined,
|
|
360
360
|
// ALI-1017: skip the in-sandbox opencode install when the image ships it.
|
|
361
361
|
prebaked: prebakedImage,
|
package/dist/manifests-cli.js
CHANGED
|
@@ -73,6 +73,66 @@ function asRecord(value) {
|
|
|
73
73
|
function num(value) {
|
|
74
74
|
return typeof value === 'number' ? value : 0;
|
|
75
75
|
}
|
|
76
|
+
// -- list table (ALI-1038) ----------------------------------------------------
|
|
77
|
+
/** "3m" / "4h" / "2d" — enough resolution to pick a manifest out of a list. */
|
|
78
|
+
function relativeAge(iso, now) {
|
|
79
|
+
if (typeof iso !== 'string')
|
|
80
|
+
return '-';
|
|
81
|
+
const then = Date.parse(iso);
|
|
82
|
+
if (Number.isNaN(then))
|
|
83
|
+
return '-';
|
|
84
|
+
const mins = Math.max(0, Math.floor((now - then) / 60_000));
|
|
85
|
+
if (mins < 60)
|
|
86
|
+
return `${mins}m`;
|
|
87
|
+
const hours = Math.floor(mins / 60);
|
|
88
|
+
if (hours < 48)
|
|
89
|
+
return `${hours}h`;
|
|
90
|
+
return `${Math.floor(hours / 24)}d`;
|
|
91
|
+
}
|
|
92
|
+
/** One-glance change summary from the stored proposedState — no live API call. */
|
|
93
|
+
function changesSummary(manifest) {
|
|
94
|
+
if (manifest.actionType !== 'repo_merge')
|
|
95
|
+
return '-';
|
|
96
|
+
const proposed = asRecord(manifest.proposedState);
|
|
97
|
+
const files = num(proposed.filesChanged);
|
|
98
|
+
const parts = `${files} file${files === 1 ? '' : 's'} +${num(proposed.additions)}/-${num(proposed.deletions)}`;
|
|
99
|
+
// Instruction files steer agent behavior — reviewers should see the flag in the list.
|
|
100
|
+
return proposed.touches_instruction_files === true ? `${parts} !instr` : parts;
|
|
101
|
+
}
|
|
102
|
+
/** Reviewable-first (pending_approval, then draft), newest first within a group. */
|
|
103
|
+
function listOrder(a, b) {
|
|
104
|
+
const rank = (m) => m.status === 'pending_approval' ? 0 : m.status === 'draft' ? 1 : 2;
|
|
105
|
+
const byRank = rank(a) - rank(b);
|
|
106
|
+
if (byRank !== 0)
|
|
107
|
+
return byRank;
|
|
108
|
+
const created = (m) => typeof m.createdAt === 'string' ? Date.parse(m.createdAt) || 0 : 0;
|
|
109
|
+
return created(b) - created(a);
|
|
110
|
+
}
|
|
111
|
+
/** Render manifests as the standard padEnd table (matches teams/projects list). */
|
|
112
|
+
function manifestsTable(manifests, now) {
|
|
113
|
+
if (manifests.length === 0)
|
|
114
|
+
return 'promotion manifests\n(none)';
|
|
115
|
+
const rows = [...manifests].sort(listOrder).map(manifest => ({
|
|
116
|
+
id: typeof manifest.id === 'string' ? manifest.id : '(unknown)',
|
|
117
|
+
status: typeof manifest.status === 'string' ? manifest.status : '(unknown)',
|
|
118
|
+
type: typeof manifest.actionType === 'string' ? manifest.actionType : '(unknown)',
|
|
119
|
+
changes: changesSummary(manifest),
|
|
120
|
+
author: typeof manifest.authorActorType === 'string' ? manifest.authorActorType : '-',
|
|
121
|
+
age: relativeAge(manifest.createdAt, now),
|
|
122
|
+
}));
|
|
123
|
+
const idWidth = Math.max('ID'.length, ...rows.map(row => row.id.length));
|
|
124
|
+
const statusWidth = Math.max('STATUS'.length, ...rows.map(row => row.status.length));
|
|
125
|
+
const typeWidth = Math.max('TYPE'.length, ...rows.map(row => row.type.length));
|
|
126
|
+
const changesWidth = Math.max('CHANGES'.length, ...rows.map(row => row.changes.length));
|
|
127
|
+
const authorWidth = Math.max('AUTHOR'.length, ...rows.map(row => row.author.length));
|
|
128
|
+
const header = `${'ID'.padEnd(idWidth)} ${'STATUS'.padEnd(statusWidth)} ${'TYPE'.padEnd(typeWidth)} ` +
|
|
129
|
+
`${'CHANGES'.padEnd(changesWidth)} ${'AUTHOR'.padEnd(authorWidth)} AGE`;
|
|
130
|
+
const divider = `${'-'.repeat(idWidth)} ${'-'.repeat(statusWidth)} ${'-'.repeat(typeWidth)} ` +
|
|
131
|
+
`${'-'.repeat(changesWidth)} ${'-'.repeat(authorWidth)} ---`;
|
|
132
|
+
const body = rows.map(row => `${row.id.padEnd(idWidth)} ${row.status.padEnd(statusWidth)} ${row.type.padEnd(typeWidth)} ` +
|
|
133
|
+
`${row.changes.padEnd(changesWidth)} ${row.author.padEnd(authorWidth)} ${row.age}`);
|
|
134
|
+
return [header, divider, ...body].join('\n');
|
|
135
|
+
}
|
|
76
136
|
// repo_merge manifests (ALI-972) render the compare summary from stored evidence
|
|
77
137
|
// — no live GitHub call — so a reviewer can approve informed without a PR.
|
|
78
138
|
function repoMergeShow(manifest) {
|
|
@@ -108,7 +168,7 @@ export async function manifestsCommand(args, io) {
|
|
|
108
168
|
const response = await fetcherFrom(io)(`/api/cli/promotion-manifests${query}`);
|
|
109
169
|
const data = await requireOk(response, 'Manifest list');
|
|
110
170
|
const manifests = Array.isArray(data.manifests) ? data.manifests : [];
|
|
111
|
-
emit(io, { manifests },
|
|
171
|
+
emit(io, { manifests }, manifestsTable(manifests, Date.now()));
|
|
112
172
|
return 0;
|
|
113
173
|
}
|
|
114
174
|
if (subcommand === 'show') {
|
|
@@ -135,7 +195,23 @@ export async function manifestsCommand(args, io) {
|
|
|
135
195
|
});
|
|
136
196
|
const data = await requireOk(response, `Manifest ${subcommand}`);
|
|
137
197
|
const manifest = (data.manifest ?? {});
|
|
138
|
-
|
|
198
|
+
const lines = [manifestLine(manifest)];
|
|
199
|
+
// Reject reaps the session branch best-effort; a failed reap must never be
|
|
200
|
+
// silent (ALI-1043) — surface it with the exact cleanup command.
|
|
201
|
+
if (subcommand === 'reject' && manifest.actionType === 'repo_merge') {
|
|
202
|
+
const outcome = asRecord(manifest.outcome);
|
|
203
|
+
const branch = asRecord(manifest.proposedState).branch;
|
|
204
|
+
if (outcome.branchDeleted === true) {
|
|
205
|
+
lines.push('session branch deleted');
|
|
206
|
+
}
|
|
207
|
+
else if (typeof branch === 'string' && branch.length > 0) {
|
|
208
|
+
lines.push(`warning: the session branch could not be deleted — remove it with: git push origin --delete ${branch}`);
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
lines.push('warning: the session branch could not be deleted (branch name unavailable)');
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
emit(io, { manifest }, lines.join('\n'));
|
|
139
215
|
return 0;
|
|
140
216
|
}
|
|
141
217
|
io.print('Usage: orizu manifests <list|show|approve|reject|apply> ... [--json]');
|
package/package.json
CHANGED
package/src/hosted-bootstrap.ts
CHANGED
|
@@ -27,6 +27,7 @@ import type { SandboxSession } from './sandbox-provider.js'
|
|
|
27
27
|
import { sweepForTokenResidue, type HygieneFinding } from './daytona-slice.js'
|
|
28
28
|
import { redactSecrets } from './secret-redaction.js'
|
|
29
29
|
import {
|
|
30
|
+
AGENT_GIT_IDENTITY,
|
|
30
31
|
BEARER_BASENAME,
|
|
31
32
|
BOOT_CONTEXT_BASENAME,
|
|
32
33
|
DEFAULT_CACHE_REFRESH_BUFFER_MS,
|
|
@@ -410,6 +411,11 @@ export async function bootstrapHostedSandbox(opts: HostedBootstrapOptions): Prom
|
|
|
410
411
|
// Persist the helper repo-LOCAL (never --global) for subsequent fetch/push.
|
|
411
412
|
await session.exec(`git -C ${workspaceDir} config credential.helper ${shellSingleQuote(helperValue)}`)
|
|
412
413
|
await session.exec(`git -C ${workspaceDir} config credential.useHttpPath true`)
|
|
414
|
+
// Repo-LOCAL agent identity: the sandbox has no ambient git identity, so
|
|
415
|
+
// without this every agent `git commit` dies with "Author identity unknown"
|
|
416
|
+
// (ALI-1036, found live in QA-3). Same identity the loop stamps on events.
|
|
417
|
+
await session.exec(`git -C ${workspaceDir} config user.name ${shellSingleQuote(AGENT_GIT_IDENTITY.name)}`)
|
|
418
|
+
await session.exec(`git -C ${workspaceDir} config user.email ${shellSingleQuote(AGENT_GIT_IDENTITY.email)}`)
|
|
413
419
|
record('repo_cloned', true, `cloned ${opts.sessionBranch}`)
|
|
414
420
|
await sink.append('repo_cloned', { branch: opts.sessionBranch, workspaceDir })
|
|
415
421
|
|
|
@@ -35,6 +35,15 @@ export const DEFAULT_CACHE_REFRESH_BUFFER_MS = 5 * 60 * 1000
|
|
|
35
35
|
* `.openinspect/setup.sh` — fresh-boot only, non-fatal, output captured). */
|
|
36
36
|
export const SETUP_HOOK_RELATIVE_PATH = '.orizu/setup.sh'
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* The git author identity for hosted-agent commits. Bootstrap configures it
|
|
40
|
+
* repo-LOCAL in the sandbox clone (never --global) so agent `git commit` works
|
|
41
|
+
* out of the box — the sandbox has no ambient identity and commits died with
|
|
42
|
+
* "Author identity unknown" (ALI-1036, found live in QA-3). The loop uses the
|
|
43
|
+
* same identity for run-event attribution; keep them identical.
|
|
44
|
+
*/
|
|
45
|
+
export const AGENT_GIT_IDENTITY = { name: 'Orizu Agent', email: 'agent@orizu.ai' } as const
|
|
46
|
+
|
|
38
47
|
// -- Pre-baked runtime marker (ALI-1017) -------------------------------------
|
|
39
48
|
|
|
40
49
|
/**
|
|
@@ -49,7 +49,7 @@ import {
|
|
|
49
49
|
type HostedRuntimePaths,
|
|
50
50
|
} from './hosted-bootstrap.js'
|
|
51
51
|
import { resumeRunEventSink } from './hosted-run-event-sink.js'
|
|
52
|
-
import { BEARER_BASENAME } from './hosted-runtime-assets.js'
|
|
52
|
+
import { AGENT_GIT_IDENTITY, BEARER_BASENAME } from './hosted-runtime-assets.js'
|
|
53
53
|
import { createVercelProvider } from './vercel-sandbox-provider.js'
|
|
54
54
|
import { buildEgressPolicy, DEFAULT_EGRESS_CANARY_HOST } from './egress-policy.js'
|
|
55
55
|
import { hostedLoopCommand, type HostedLoopContext } from './hosted-loop.js'
|
|
@@ -534,7 +534,7 @@ export async function startHostedSession(
|
|
|
534
534
|
model,
|
|
535
535
|
reasoningEffort: opts.reasoningEffort,
|
|
536
536
|
messageId: `${runId}:task`,
|
|
537
|
-
author:
|
|
537
|
+
author: AGENT_GIT_IDENTITY,
|
|
538
538
|
anthropicDummyKey: opts.modelApiKey ? ANTHROPIC_DUMMY_KEY : undefined,
|
|
539
539
|
// ALI-1017: skip the in-sandbox opencode install when the image ships it.
|
|
540
540
|
prebaked: prebakedImage,
|
package/src/manifests-cli.ts
CHANGED
|
@@ -93,6 +93,71 @@ function num(value: unknown): number {
|
|
|
93
93
|
return typeof value === 'number' ? value : 0
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
// -- list table (ALI-1038) ----------------------------------------------------
|
|
97
|
+
|
|
98
|
+
/** "3m" / "4h" / "2d" — enough resolution to pick a manifest out of a list. */
|
|
99
|
+
function relativeAge(iso: unknown, now: number): string {
|
|
100
|
+
if (typeof iso !== 'string') return '-'
|
|
101
|
+
const then = Date.parse(iso)
|
|
102
|
+
if (Number.isNaN(then)) return '-'
|
|
103
|
+
const mins = Math.max(0, Math.floor((now - then) / 60_000))
|
|
104
|
+
if (mins < 60) return `${mins}m`
|
|
105
|
+
const hours = Math.floor(mins / 60)
|
|
106
|
+
if (hours < 48) return `${hours}h`
|
|
107
|
+
return `${Math.floor(hours / 24)}d`
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** One-glance change summary from the stored proposedState — no live API call. */
|
|
111
|
+
function changesSummary(manifest: Record<string, unknown>): string {
|
|
112
|
+
if (manifest.actionType !== 'repo_merge') return '-'
|
|
113
|
+
const proposed = asRecord(manifest.proposedState)
|
|
114
|
+
const files = num(proposed.filesChanged)
|
|
115
|
+
const parts = `${files} file${files === 1 ? '' : 's'} +${num(proposed.additions)}/-${num(proposed.deletions)}`
|
|
116
|
+
// Instruction files steer agent behavior — reviewers should see the flag in the list.
|
|
117
|
+
return proposed.touches_instruction_files === true ? `${parts} !instr` : parts
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Reviewable-first (pending_approval, then draft), newest first within a group. */
|
|
121
|
+
function listOrder(a: Record<string, unknown>, b: Record<string, unknown>): number {
|
|
122
|
+
const rank = (m: Record<string, unknown>): number =>
|
|
123
|
+
m.status === 'pending_approval' ? 0 : m.status === 'draft' ? 1 : 2
|
|
124
|
+
const byRank = rank(a) - rank(b)
|
|
125
|
+
if (byRank !== 0) return byRank
|
|
126
|
+
const created = (m: Record<string, unknown>): number =>
|
|
127
|
+
typeof m.createdAt === 'string' ? Date.parse(m.createdAt) || 0 : 0
|
|
128
|
+
return created(b) - created(a)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Render manifests as the standard padEnd table (matches teams/projects list). */
|
|
132
|
+
function manifestsTable(manifests: Record<string, unknown>[], now: number): string {
|
|
133
|
+
if (manifests.length === 0) return 'promotion manifests\n(none)'
|
|
134
|
+
const rows = [...manifests].sort(listOrder).map(manifest => ({
|
|
135
|
+
id: typeof manifest.id === 'string' ? manifest.id : '(unknown)',
|
|
136
|
+
status: typeof manifest.status === 'string' ? manifest.status : '(unknown)',
|
|
137
|
+
type: typeof manifest.actionType === 'string' ? manifest.actionType : '(unknown)',
|
|
138
|
+
changes: changesSummary(manifest),
|
|
139
|
+
author: typeof manifest.authorActorType === 'string' ? manifest.authorActorType : '-',
|
|
140
|
+
age: relativeAge(manifest.createdAt, now),
|
|
141
|
+
}))
|
|
142
|
+
const idWidth = Math.max('ID'.length, ...rows.map(row => row.id.length))
|
|
143
|
+
const statusWidth = Math.max('STATUS'.length, ...rows.map(row => row.status.length))
|
|
144
|
+
const typeWidth = Math.max('TYPE'.length, ...rows.map(row => row.type.length))
|
|
145
|
+
const changesWidth = Math.max('CHANGES'.length, ...rows.map(row => row.changes.length))
|
|
146
|
+
const authorWidth = Math.max('AUTHOR'.length, ...rows.map(row => row.author.length))
|
|
147
|
+
const header =
|
|
148
|
+
`${'ID'.padEnd(idWidth)} ${'STATUS'.padEnd(statusWidth)} ${'TYPE'.padEnd(typeWidth)} ` +
|
|
149
|
+
`${'CHANGES'.padEnd(changesWidth)} ${'AUTHOR'.padEnd(authorWidth)} AGE`
|
|
150
|
+
const divider =
|
|
151
|
+
`${'-'.repeat(idWidth)} ${'-'.repeat(statusWidth)} ${'-'.repeat(typeWidth)} ` +
|
|
152
|
+
`${'-'.repeat(changesWidth)} ${'-'.repeat(authorWidth)} ---`
|
|
153
|
+
const body = rows.map(
|
|
154
|
+
row =>
|
|
155
|
+
`${row.id.padEnd(idWidth)} ${row.status.padEnd(statusWidth)} ${row.type.padEnd(typeWidth)} ` +
|
|
156
|
+
`${row.changes.padEnd(changesWidth)} ${row.author.padEnd(authorWidth)} ${row.age}`
|
|
157
|
+
)
|
|
158
|
+
return [header, divider, ...body].join('\n')
|
|
159
|
+
}
|
|
160
|
+
|
|
96
161
|
// repo_merge manifests (ALI-972) render the compare summary from stored evidence
|
|
97
162
|
// — no live GitHub call — so a reviewer can approve informed without a PR.
|
|
98
163
|
function repoMergeShow(manifest: Record<string, unknown>): string {
|
|
@@ -130,7 +195,7 @@ export async function manifestsCommand(args: string[], io: ManifestsCommandIo):
|
|
|
130
195
|
const response = await fetcherFrom(io)(`/api/cli/promotion-manifests${query}`)
|
|
131
196
|
const data = await requireOk(response, 'Manifest list')
|
|
132
197
|
const manifests = Array.isArray(data.manifests) ? (data.manifests as Record<string, unknown>[]) : []
|
|
133
|
-
emit(io, { manifests },
|
|
198
|
+
emit(io, { manifests }, manifestsTable(manifests, Date.now()))
|
|
134
199
|
return 0
|
|
135
200
|
}
|
|
136
201
|
|
|
@@ -159,7 +224,23 @@ export async function manifestsCommand(args: string[], io: ManifestsCommandIo):
|
|
|
159
224
|
})
|
|
160
225
|
const data = await requireOk(response, `Manifest ${subcommand}`)
|
|
161
226
|
const manifest = (data.manifest ?? {}) as Record<string, unknown>
|
|
162
|
-
|
|
227
|
+
const lines = [manifestLine(manifest)]
|
|
228
|
+
// Reject reaps the session branch best-effort; a failed reap must never be
|
|
229
|
+
// silent (ALI-1043) — surface it with the exact cleanup command.
|
|
230
|
+
if (subcommand === 'reject' && manifest.actionType === 'repo_merge') {
|
|
231
|
+
const outcome = asRecord(manifest.outcome)
|
|
232
|
+
const branch = asRecord(manifest.proposedState).branch
|
|
233
|
+
if (outcome.branchDeleted === true) {
|
|
234
|
+
lines.push('session branch deleted')
|
|
235
|
+
} else if (typeof branch === 'string' && branch.length > 0) {
|
|
236
|
+
lines.push(
|
|
237
|
+
`warning: the session branch could not be deleted — remove it with: git push origin --delete ${branch}`
|
|
238
|
+
)
|
|
239
|
+
} else {
|
|
240
|
+
lines.push('warning: the session branch could not be deleted (branch name unavailable)')
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
emit(io, { manifest }, lines.join('\n'))
|
|
163
244
|
return 0
|
|
164
245
|
}
|
|
165
246
|
|