@peakflo/agent-harness 0.2.9-rc.6 → 0.2.9-rc.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/README.md
CHANGED
|
@@ -126,6 +126,11 @@ An approval request says what will happen in its first line ("Run: npm test",
|
|
|
126
126
|
Codex can send. "Deny" refuses the action and lets the turn go on, so the agent
|
|
127
127
|
says that it was not allowed; the denied action never runs.
|
|
128
128
|
|
|
129
|
+
A command too long for the card keeps its start and its end, and the card
|
|
130
|
+
says it is not shown in full. A question Codex asks the person is shown as a
|
|
131
|
+
question to answer in words, not as Allow / Deny. An MCP form that needs values
|
|
132
|
+
is declined, because nobody can fill it in from the chat.
|
|
133
|
+
|
|
129
134
|
Cursor reports an account limit as a normal reply ("Upgrade your plan to
|
|
130
135
|
continue"). The runner turns a reply that is only that sentence into a failed
|
|
131
136
|
turn that says it comes from the Cursor account on the runner.
|
|
@@ -29,6 +29,11 @@ export declare function isStandingApprovalDecision(optionId: string): boolean;
|
|
|
29
29
|
* any file change — is sent to the client as an approval request before it
|
|
30
30
|
* runs. That is what "Ask before each action" says.
|
|
31
31
|
*/
|
|
32
|
+
/**
|
|
33
|
+
* The fields an MCP form asks the person to fill in. Empty for a form with no
|
|
34
|
+
* fields, and for a request that is not a form (`mode: 'url'`).
|
|
35
|
+
*/
|
|
36
|
+
export declare function elicitationFormFields(params: Record<string, unknown>): string[];
|
|
32
37
|
export declare function codexApprovalPolicy(config: Pick<SessionConfig, 'permissionMode'>): 'never' | 'untrusted';
|
|
33
38
|
interface PendingApproval {
|
|
34
39
|
requestId: string | number;
|
|
@@ -89,6 +94,19 @@ export declare class CodexAppServerAdapter implements CodingAgentAdapter {
|
|
|
89
94
|
private setupStdoutParser;
|
|
90
95
|
private handleRpcMessage;
|
|
91
96
|
private handleServerRequest;
|
|
97
|
+
/**
|
|
98
|
+
* ADDED, not in 20x: `item/tool/requestUserInput` IS A QUESTION.
|
|
99
|
+
*
|
|
100
|
+
* It was shown as a permission card, and "Allow" sent the word "accept" to
|
|
101
|
+
* the model as the person's answer. It is now a question part, like the
|
|
102
|
+
* ones Pi asks: the person picks an option or types, and the answer goes
|
|
103
|
+
* back through `respondToQuestion` in the form this request takes
|
|
104
|
+
* (`{ answers: { <question id>: { answers: [text] } } }`). It is asked in
|
|
105
|
+
* every permission mode, bypass included: nobody can answer it but a person.
|
|
106
|
+
*/
|
|
107
|
+
private handleUserInputRequest;
|
|
108
|
+
/** The answer to a question asked with `item/tool/requestUserInput`. */
|
|
109
|
+
respondToQuestion(sessionId: string, answers: Record<string, string>, _config?: SessionConfig, requestId?: string): Promise<boolean>;
|
|
92
110
|
private handleNotification;
|
|
93
111
|
private handleApprovalRequest;
|
|
94
112
|
private buildApprovalResponse;
|
|
@@ -43,6 +43,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
43
43
|
exports.CodexAppServerAdapter = void 0;
|
|
44
44
|
exports.unwrapListedThreadItem = unwrapListedThreadItem;
|
|
45
45
|
exports.isStandingApprovalDecision = isStandingApprovalDecision;
|
|
46
|
+
exports.elicitationFormFields = elicitationFormFields;
|
|
46
47
|
exports.codexApprovalPolicy = codexApprovalPolicy;
|
|
47
48
|
/**
|
|
48
49
|
* Extracted verbatim from 20x `src/main/adapters/codex-app-server-adapter.ts`.
|
|
@@ -106,6 +107,26 @@ function isStandingApprovalDecision(optionId) {
|
|
|
106
107
|
* any file change — is sent to the client as an approval request before it
|
|
107
108
|
* runs. That is what "Ask before each action" says.
|
|
108
109
|
*/
|
|
110
|
+
/**
|
|
111
|
+
* The fields an MCP form asks the person to fill in. Empty for a form with no
|
|
112
|
+
* fields, and for a request that is not a form (`mode: 'url'`).
|
|
113
|
+
*/
|
|
114
|
+
function elicitationFormFields(params) {
|
|
115
|
+
// Only a request that says it is NOT a form is taken at its word. A missing
|
|
116
|
+
// or unknown mode is read from the schema it carries, like a form.
|
|
117
|
+
if (asString(params.mode) === 'url')
|
|
118
|
+
return [];
|
|
119
|
+
const schema = params.requestedSchema;
|
|
120
|
+
if (!isObject(schema)) {
|
|
121
|
+
// A form whose shape this build cannot read is treated as having fields.
|
|
122
|
+
return schema === undefined || schema === null ? [] : ['an unreadable form'];
|
|
123
|
+
}
|
|
124
|
+
const properties = isObject(schema.properties) ? Object.keys(schema.properties) : [];
|
|
125
|
+
const required = Array.isArray(schema.required)
|
|
126
|
+
? schema.required.filter((name) => typeof name === 'string')
|
|
127
|
+
: [];
|
|
128
|
+
return [...new Set([...required, ...properties])];
|
|
129
|
+
}
|
|
109
130
|
function codexApprovalPolicy(config) {
|
|
110
131
|
// `'allow'` is what `mapPermissionMode` makes of the agent's
|
|
111
132
|
// `bypassPermissions` ("Don't ask for permission"), and of nothing else.
|
|
@@ -803,12 +824,113 @@ class CodexAppServerAdapter {
|
|
|
803
824
|
this.handleApprovalRequest(session, request, params);
|
|
804
825
|
return;
|
|
805
826
|
}
|
|
806
|
-
if (request.method === '
|
|
827
|
+
if (request.method === 'item/tool/requestUserInput') {
|
|
828
|
+
this.handleUserInputRequest(session, request, params);
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
if (request.method === 'mcpServer/elicitation/request') {
|
|
832
|
+
/*
|
|
833
|
+
* CHANGED from 20x: A FORM THAT NEEDS VALUES IS DECLINED, NOT "ALLOWED".
|
|
834
|
+
*
|
|
835
|
+
* An elicitation in form mode asks the person to fill in fields. This
|
|
836
|
+
* runner can relay a yes or a no, not field values, and "Allow" answered
|
|
837
|
+
* with an empty form. A form with fields is therefore declined at once,
|
|
838
|
+
* in every permission mode: the MCP server is told no, rather than
|
|
839
|
+
* being handed values nobody entered. A form with no fields is a plain
|
|
840
|
+
* confirmation (how an MCP tool call asks to run) and is still put to
|
|
841
|
+
* the person.
|
|
842
|
+
*/
|
|
843
|
+
const fields = elicitationFormFields(params);
|
|
844
|
+
if (fields.length > 0) {
|
|
845
|
+
console.warn(`[CodexAppServerAdapter] Declining an MCP form from ${asString(params.serverName) || 'a server'}: ` +
|
|
846
|
+
`it asks for ${fields.join(', ')}, which cannot be entered from here`);
|
|
847
|
+
this.sendRpcResponse(session, request.id, { action: 'decline' });
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
807
850
|
this.handleApprovalRequest(session, request, params);
|
|
808
851
|
return;
|
|
809
852
|
}
|
|
810
853
|
this.sendRpcResponse(session, request.id, {});
|
|
811
854
|
}
|
|
855
|
+
/**
|
|
856
|
+
* ADDED, not in 20x: `item/tool/requestUserInput` IS A QUESTION.
|
|
857
|
+
*
|
|
858
|
+
* It was shown as a permission card, and "Allow" sent the word "accept" to
|
|
859
|
+
* the model as the person's answer. It is now a question part, like the
|
|
860
|
+
* ones Pi asks: the person picks an option or types, and the answer goes
|
|
861
|
+
* back through `respondToQuestion` in the form this request takes
|
|
862
|
+
* (`{ answers: { <question id>: { answers: [text] } } }`). It is asked in
|
|
863
|
+
* every permission mode, bypass included: nobody can answer it but a person.
|
|
864
|
+
*/
|
|
865
|
+
handleUserInputRequest(session, request, params) {
|
|
866
|
+
const asked = (Array.isArray(params.questions) ? params.questions : []).filter(isObject);
|
|
867
|
+
/*
|
|
868
|
+
* A SECRET IS NOT ASKED FOR IN THE CHAT. An answer typed here is stored in
|
|
869
|
+
* the transcript and shown to everyone who can read the chat; a question
|
|
870
|
+
* marked `isSecret` asks for something that must not be. The whole request
|
|
871
|
+
* is left unanswered instead.
|
|
872
|
+
*/
|
|
873
|
+
if (asked.some((question) => question.isSecret === true)) {
|
|
874
|
+
console.warn('[CodexAppServerAdapter] Not asking a question marked secret; it is left unanswered');
|
|
875
|
+
this.sendRpcResponse(session, request.id, { answers: {} });
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
// The card sends each answer back under its header, so headers are made
|
|
879
|
+
// unique here: two questions called the same would get one answer.
|
|
880
|
+
const usedHeaders = new Map();
|
|
881
|
+
const uniqueHeader = (wanted) => {
|
|
882
|
+
const count = (usedHeaders.get(wanted) ?? 0) + 1;
|
|
883
|
+
usedHeaders.set(wanted, count);
|
|
884
|
+
return count === 1 ? wanted : `${wanted} (${count})`;
|
|
885
|
+
};
|
|
886
|
+
const questions = asked
|
|
887
|
+
.map((question, index) => ({
|
|
888
|
+
id: asString(question.id) || `q${index + 1}`,
|
|
889
|
+
header: uniqueHeader(asString(question.header) || `Question ${index + 1}`),
|
|
890
|
+
question: asString(question.question) || '',
|
|
891
|
+
options: (Array.isArray(question.options) ? question.options : [])
|
|
892
|
+
.filter(isObject)
|
|
893
|
+
.map((option) => ({
|
|
894
|
+
label: asString(option.label) || '',
|
|
895
|
+
description: asString(option.description) || ''
|
|
896
|
+
}))
|
|
897
|
+
.filter((option) => option.label)
|
|
898
|
+
}));
|
|
899
|
+
if (questions.length === 0) {
|
|
900
|
+
// Nothing to ask. An empty answer set, never an invented one.
|
|
901
|
+
this.sendRpcResponse(session, request.id, { answers: {} });
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
// An earlier question that was never answered gets no answer.
|
|
905
|
+
if (session.pendingQuestion) {
|
|
906
|
+
this.sendRpcResponse(session, session.pendingQuestion.requestId, { answers: {} });
|
|
907
|
+
}
|
|
908
|
+
const partId = `codex-question-${asString(params.itemId) || String(request.id)}`;
|
|
909
|
+
session.pendingQuestion = { requestId: request.id, partId, questions };
|
|
910
|
+
session.status = coding_agent_adapter_1.SessionStatusType.WAITING_APPROVAL;
|
|
911
|
+
this.addEvent(session, { method: 'pf/question', params: { partId, questions } });
|
|
912
|
+
}
|
|
913
|
+
/** The answer to a question asked with `item/tool/requestUserInput`. */
|
|
914
|
+
async respondToQuestion(sessionId, answers, _config, requestId) {
|
|
915
|
+
const session = this.sessions.get(sessionId);
|
|
916
|
+
const pending = session?.pendingQuestion;
|
|
917
|
+
if (!session || !pending)
|
|
918
|
+
return false;
|
|
919
|
+
if (requestId && requestId !== pending.partId)
|
|
920
|
+
return false;
|
|
921
|
+
// The card sends `Header: answer` per question, or one bare answer.
|
|
922
|
+
const result = {};
|
|
923
|
+
for (const question of pending.questions) {
|
|
924
|
+
const answer = (answers[question.header] ??
|
|
925
|
+
(pending.questions.length === 1 ? answers.answer : undefined) ??
|
|
926
|
+
'').trim();
|
|
927
|
+
result[question.id] = { answers: answer ? [answer] : [] };
|
|
928
|
+
}
|
|
929
|
+
this.sendRpcResponse(session, pending.requestId, { answers: result });
|
|
930
|
+
session.pendingQuestion = null;
|
|
931
|
+
session.status = coding_agent_adapter_1.SessionStatusType.BUSY;
|
|
932
|
+
return true;
|
|
933
|
+
}
|
|
812
934
|
handleNotification(session, notification) {
|
|
813
935
|
const params = isObject(notification.params) ? notification.params : {};
|
|
814
936
|
if (notification.method === 'thread/status/changed') {
|
|
@@ -822,6 +944,9 @@ class CodexAppServerAdapter {
|
|
|
822
944
|
}
|
|
823
945
|
if (notification.method === 'turn/completed') {
|
|
824
946
|
session.activeTurnId = null;
|
|
947
|
+
// The turn is over, so a question it asked can no longer be answered:
|
|
948
|
+
// the request is gone on Codex's side. A late answer finds nothing.
|
|
949
|
+
session.pendingQuestion = null;
|
|
825
950
|
if (session.threadId) {
|
|
826
951
|
session.pendingCompletionRefreshes += 1;
|
|
827
952
|
void this.reconcileCompletedTurn(session);
|
|
@@ -853,6 +978,13 @@ class CodexAppServerAdapter {
|
|
|
853
978
|
}
|
|
854
979
|
if (notification.method === 'serverRequest/resolved') {
|
|
855
980
|
const requestId = params.requestId;
|
|
981
|
+
// Codex settled the question itself (the turn was interrupted).
|
|
982
|
+
if (session.pendingQuestion && String(session.pendingQuestion.requestId) === String(requestId)) {
|
|
983
|
+
session.pendingQuestion = null;
|
|
984
|
+
if (session.status === coding_agent_adapter_1.SessionStatusType.WAITING_APPROVAL) {
|
|
985
|
+
session.status = coding_agent_adapter_1.SessionStatusType.BUSY;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
856
988
|
if (session.pendingApproval && String(session.pendingApproval.requestId) === String(requestId)) {
|
|
857
989
|
session.pendingApproval = null;
|
|
858
990
|
if (session.status === coding_agent_adapter_1.SessionStatusType.WAITING_APPROVAL) {
|
|
@@ -1184,6 +1316,29 @@ class CodexAppServerAdapter {
|
|
|
1184
1316
|
if ((method === 'item/started' || method === 'item/completed') && item) {
|
|
1185
1317
|
return this.convertThreadItem(method, item, params, seenMessageIds, seenPartIds, partContentLengths, session);
|
|
1186
1318
|
}
|
|
1319
|
+
if (method === 'pf/question') {
|
|
1320
|
+
const partId = asString(params.partId) || 'codex-question';
|
|
1321
|
+
if (seenPartIds.has(partId))
|
|
1322
|
+
return [];
|
|
1323
|
+
seenPartIds.add(partId);
|
|
1324
|
+
const questions = Array.isArray(params.questions) ? params.questions.filter(isObject) : [];
|
|
1325
|
+
return [{
|
|
1326
|
+
id: partId,
|
|
1327
|
+
type: coding_agent_adapter_1.MessagePartType.QUESTION,
|
|
1328
|
+
role: coding_agent_adapter_1.MessageRole.ASSISTANT,
|
|
1329
|
+
text: questions.map((question) => asString(question.question) || '').join('\n'),
|
|
1330
|
+
tool: {
|
|
1331
|
+
name: 'question',
|
|
1332
|
+
status: 'running',
|
|
1333
|
+
title: asString(questions[0]?.header) || 'Question',
|
|
1334
|
+
questions: questions.map((question) => ({
|
|
1335
|
+
header: question.header,
|
|
1336
|
+
question: question.question,
|
|
1337
|
+
options: question.options
|
|
1338
|
+
}))
|
|
1339
|
+
}
|
|
1340
|
+
}];
|
|
1341
|
+
}
|
|
1187
1342
|
if (method === 'error') {
|
|
1188
1343
|
const partId = `error-${Date.now()}`;
|
|
1189
1344
|
return [{
|
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
* Kept out of the adapter, which is a copy of 20x's, so it can be tested alone.
|
|
22
22
|
*/
|
|
23
23
|
type Json = Record<string, unknown>;
|
|
24
|
+
/** The longest command shown whole. Longer ones keep both ends. */
|
|
25
|
+
export declare const MAX_SHOWN_COMMAND = 2000;
|
|
26
|
+
/** The longest reason, message or question shown whole. */
|
|
27
|
+
export declare const MAX_SHOWN_NOTE = 500;
|
|
24
28
|
interface FileChange {
|
|
25
29
|
path: string;
|
|
26
30
|
kind: string;
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
* Kept out of the adapter, which is a copy of 20x's, so it can be tested alone.
|
|
23
23
|
*/
|
|
24
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.MAX_SHOWN_NOTE = exports.MAX_SHOWN_COMMAND = void 0;
|
|
25
26
|
exports.readFileChanges = readFileChanges;
|
|
26
27
|
exports.describeCodexApproval = describeCodexApproval;
|
|
27
28
|
function isRecord(value) {
|
|
@@ -35,11 +36,49 @@ function oneLine(value) {
|
|
|
35
36
|
return value.replace(/\s*\n\s*/g, ' ⏎ ').trim();
|
|
36
37
|
}
|
|
37
38
|
const MAX_LISTED_PATHS = 12;
|
|
39
|
+
/** The longest command shown whole. Longer ones keep both ends. */
|
|
40
|
+
exports.MAX_SHOWN_COMMAND = 2000;
|
|
41
|
+
const COMMAND_HEAD = 1500;
|
|
42
|
+
const COMMAND_TAIL = 300;
|
|
43
|
+
/** The longest reason, message or question shown whole. */
|
|
44
|
+
exports.MAX_SHOWN_NOTE = 500;
|
|
45
|
+
/**
|
|
46
|
+
* A command that is too long for a card keeps its start, where the program
|
|
47
|
+
* and its first arguments are, and its end, where a redirect or a second
|
|
48
|
+
* command would be, and says how much is missing. The card then also says, on
|
|
49
|
+
* a line of its own, that this is not the whole command: a person must never
|
|
50
|
+
* approve a shortened command believing they have read it.
|
|
51
|
+
*/
|
|
52
|
+
function shownCommand(command) {
|
|
53
|
+
const line = oneLine(command);
|
|
54
|
+
if (line.length <= exports.MAX_SHOWN_COMMAND)
|
|
55
|
+
return { text: line, hidden: 0 };
|
|
56
|
+
const hidden = line.length - COMMAND_HEAD - COMMAND_TAIL;
|
|
57
|
+
return {
|
|
58
|
+
text: `${line.slice(0, COMMAND_HEAD)} … [${hidden} characters not shown] … ${line.slice(-COMMAND_TAIL)}`,
|
|
59
|
+
hidden
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function notShownInFull(command) {
|
|
63
|
+
return (`Not shown in full: this command is ${oneLine(command).length} characters ` +
|
|
64
|
+
'long. Deny it unless you know what it runs.');
|
|
65
|
+
}
|
|
66
|
+
/** Words about the request (why, what a server says), not what will run. */
|
|
67
|
+
function note(value) {
|
|
68
|
+
const line = oneLine(value);
|
|
69
|
+
return line.length <= exports.MAX_SHOWN_NOTE
|
|
70
|
+
? line
|
|
71
|
+
: `${line.slice(0, exports.MAX_SHOWN_NOTE)} … (shortened, ${line.length - exports.MAX_SHOWN_NOTE} more characters)`;
|
|
72
|
+
}
|
|
38
73
|
/** `/work/space/src/a.ts` in `/work/space` → `src/a.ts`. Others unchanged. */
|
|
39
74
|
function relativeTo(workspaceDir, path) {
|
|
40
75
|
const root = (workspaceDir ?? '').replace(/\/+$/, '');
|
|
41
76
|
if (!root)
|
|
42
77
|
return path;
|
|
78
|
+
// A path that climbs (`/work/space/../../etc/passwd`) is not inside the
|
|
79
|
+
// workspace whatever it starts with. Shown as it is.
|
|
80
|
+
if (/(^|\/)\.\.?(\/|$)/.test(path.replace(/\/\.$/, '')))
|
|
81
|
+
return path;
|
|
43
82
|
// macOS reports the same directory as /tmp/… and /private/tmp/….
|
|
44
83
|
for (const prefix of [
|
|
45
84
|
root,
|
|
@@ -161,11 +200,14 @@ function describeFileChanges(changes) {
|
|
|
161
200
|
* literally what the shell will be given.
|
|
162
201
|
*/
|
|
163
202
|
function unwrapShell(command) {
|
|
164
|
-
|
|
203
|
+
// SINGLE quotes only: inside them the shell changes nothing, so the inner
|
|
204
|
+
// text is exactly the command. Inside double quotes it would still expand
|
|
205
|
+
// `$(…)`, backticks and variables, and the card would not show what runs.
|
|
206
|
+
const match = /^(?:\/(?:usr\/)?bin\/)?(?:zsh|bash|sh) -l?c '([\s\S]*)'$/.exec(command.trim());
|
|
165
207
|
if (!match)
|
|
166
208
|
return command;
|
|
167
|
-
const
|
|
168
|
-
return inner.includes(
|
|
209
|
+
const inner = match[1];
|
|
210
|
+
return inner.includes("'") ? command : inner;
|
|
169
211
|
}
|
|
170
212
|
function commandText(value) {
|
|
171
213
|
if (Array.isArray(value)) {
|
|
@@ -239,11 +281,18 @@ workspaceDir) {
|
|
|
239
281
|
const network = isRecord(params.networkApprovalContext)
|
|
240
282
|
? params.networkApprovalContext
|
|
241
283
|
: null;
|
|
284
|
+
const capped = shownCommand(command);
|
|
242
285
|
if (params.kind === 'writeStdin') {
|
|
243
|
-
lines.push(`Send input to a running command: ${
|
|
286
|
+
lines.push(`Send input to a running command: ${capped.text}`);
|
|
287
|
+
if (capped.hidden)
|
|
288
|
+
lines.push(notShownInFull(command));
|
|
244
289
|
}
|
|
245
290
|
else if (command) {
|
|
246
|
-
lines.push(`Run: ${
|
|
291
|
+
lines.push(`Run: ${capped.text}`);
|
|
292
|
+
// Straight under the command, before anything else that could push it
|
|
293
|
+
// out of sight.
|
|
294
|
+
if (capped.hidden)
|
|
295
|
+
lines.push(notShownInFull(command));
|
|
247
296
|
}
|
|
248
297
|
else if (network) {
|
|
249
298
|
lines.push(`Network access: ${text(network.host)}`);
|
|
@@ -284,7 +333,7 @@ workspaceDir) {
|
|
|
284
333
|
const server = text(params.serverName) || 'An MCP server';
|
|
285
334
|
const message = text(params.message);
|
|
286
335
|
lines.push(message
|
|
287
|
-
? `${server} asks: ${
|
|
336
|
+
? `${server} asks: ${note(message)}`
|
|
288
337
|
: `${server} asks to continue`);
|
|
289
338
|
const url = text(params.url);
|
|
290
339
|
if (url)
|
|
@@ -295,20 +344,23 @@ workspaceDir) {
|
|
|
295
344
|
const question = isRecord(first)
|
|
296
345
|
? text(first.question) || text(first.header)
|
|
297
346
|
: '';
|
|
298
|
-
lines.push(question ? `Codex asks: ${
|
|
347
|
+
lines.push(question ? `Codex asks: ${note(question)}` : 'Codex asks a question');
|
|
299
348
|
}
|
|
300
349
|
else {
|
|
301
350
|
// A kind this build does not know. Say what is asked in plain words and
|
|
302
351
|
// show what the request itself says about it, never the method name alone.
|
|
303
352
|
const command = commandText(params.command);
|
|
304
353
|
lines.push(`Codex asks to approve: ${humanise(method)}`);
|
|
305
|
-
if (command)
|
|
306
|
-
lines.push(`Command: ${
|
|
354
|
+
if (command) {
|
|
355
|
+
lines.push(`Command: ${shownCommand(command).text}`);
|
|
356
|
+
if (shownCommand(command).hidden)
|
|
357
|
+
lines.push(notShownInFull(command));
|
|
358
|
+
}
|
|
307
359
|
const cwd = text(params.cwd);
|
|
308
360
|
if (cwd)
|
|
309
361
|
lines.push(`In: ${cwd}`);
|
|
310
362
|
}
|
|
311
363
|
if (reason)
|
|
312
|
-
lines.push(`Reason: ${
|
|
364
|
+
lines.push(`Reason: ${note(reason)}`);
|
|
313
365
|
return lines.join('\n');
|
|
314
366
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peakflo/agent-harness",
|
|
3
|
-
"version": "0.2.9-rc.
|
|
3
|
+
"version": "0.2.9-rc.7",
|
|
4
4
|
"description": "Headless Peakflo coding-agent harness. One codebase, three hosting modes: a Daytona cloud sandbox, a customer VM, and the 20x desktop.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|