@agent-native/core 0.210.0 → 0.210.1-nightly-20261020010058
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/action.js +16 -1
- package/dist/agent/production-agent.d.ts +1 -0
- package/dist/agent/production-agent.js +2 -0
- package/dist/agent/reaped-run-thread.d.ts +1 -1
- package/dist/agent/reaped-run-thread.js +21 -6
- package/dist/agent/run-store.fencing.worker.js +58 -0
- package/dist/agent/run-store.js +7 -5
- package/dist/agent/run-thread-projector.d.ts +27 -0
- package/dist/agent/run-thread-projector.js +79 -0
- package/dist/agent/thread-data-builder.js +38 -13
- package/dist/automation/index.d.ts +1 -1
- package/dist/cli/create.d.ts +1 -1
- package/dist/cli/create.js +30 -27
- package/dist/cli/package-lifecycle.js +3 -2
- package/dist/cli/setup-agents.d.ts +5 -0
- package/dist/cli/setup-agents.js +91 -34
- package/dist/cli/skills.js +8 -14
- package/dist/cli/tar-command.d.ts +1 -0
- package/dist/cli/tar-command.js +9 -0
- package/dist/cli/template-baseline.js +6 -3
- package/dist/cli/template-sync.d.ts +2 -1
- package/dist/cli/template-sync.js +2 -1
- package/dist/client/agent-chat.d.ts +1 -0
- package/dist/client/agent-chat.js +1 -1
- package/dist/client/api-path.d.ts +1 -0
- package/dist/client/api-path.js +6 -0
- package/dist/client/chat/agentkit-agent-native.js +6 -2
- package/dist/client/chat/runtime.js +3 -0
- package/dist/client/use-agent-chat-context.js +6 -3
- package/dist/client/use-db-sync.js +3 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +2 -2
- package/dist/deploy/immutable-assets.d.ts +1 -1
- package/dist/deploy/immutable-assets.js +1 -1
- package/dist/deploy/workspace-deploy.js +12 -5
- package/dist/file-upload/actions/upload-image.d.ts +3 -3
- package/dist/jobs/actions/manage-recurring-job.d.ts +1 -1
- package/dist/jobs/background-automation-runner.js +16 -7
- package/dist/mcp/build-server.js +5 -1
- package/dist/mcp/embed-app.js +84 -17
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +7 -7
- package/dist/org/actions/invite-organization-member.js +1 -0
- package/dist/org/actions/list-organization-members.js +1 -0
- package/dist/org/index.d.ts +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +6 -6
- package/dist/provider-api/actions/provider-api.d.ts +8 -8
- package/dist/scripts/db/exec.js +18 -6
- package/dist/scripts/db/patch.js +20 -8
- package/dist/scripts/db/postgres-client.d.ts +2 -0
- package/dist/scripts/db/postgres-client.js +17 -1
- package/dist/scripts/db/query.js +7 -6
- package/dist/scripts/db/safety.js +2 -1
- package/dist/secrets/routes.d.ts +10 -10
- package/dist/server/agent-chat-plugin.js +17 -8
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-engine-default-model-route.d.ts +1 -1
- package/dist/server/agent-teams.js +9 -3
- package/dist/server/framework-request-handler.js +19 -0
- package/dist/server/fusion-app.d.ts +7 -0
- package/dist/server/fusion-app.js +11 -2
- package/dist/templates/builder-code-starter/.agents/skills/actions/SKILL.md.patch +1 -1
- package/dist/templates/builder-code-starter/app/hooks/use-navigation-state.ts.patch +26 -45
- package/dist/templates/builder-code-starter/server/plugins/agent-chat.ts.patch +2 -1
- package/dist/templates/chat/.agents/skills/context-awareness/SKILL.md +5 -0
- package/dist/templates/chat/app/components/chat/ChatHomeRedirect.tsx +2 -2
- package/dist/templates/chat/app/hooks/use-navigation-state.ts +51 -22
- package/dist/templates/chat/server/plugins/agent-chat.ts +1 -0
- package/dist/templates/default/.agents/skills/context-awareness/SKILL.md +5 -0
- package/dist/templates/workspace-core/.agents/skills/context-awareness/SKILL.md +5 -0
- package/dist/triggers/actions/manage-automation.d.ts +5 -5
- package/package.json +5 -5
- package/src/templates/builder-code-starter/.agents/skills/actions/SKILL.md.patch +1 -1
- package/src/templates/builder-code-starter/app/hooks/use-navigation-state.ts.patch +26 -45
- package/src/templates/builder-code-starter/server/plugins/agent-chat.ts.patch +2 -1
- package/src/templates/chat/.agents/skills/context-awareness/SKILL.md +5 -0
- package/src/templates/chat/app/components/chat/ChatHomeRedirect.tsx +2 -2
- package/src/templates/chat/app/hooks/use-navigation-state.ts +51 -22
- package/src/templates/chat/server/plugins/agent-chat.ts +1 -0
- package/src/templates/default/.agents/skills/context-awareness/SKILL.md +5 -0
- package/src/templates/workspace-core/.agents/skills/context-awareness/SKILL.md +5 -0
package/dist/action.js
CHANGED
|
@@ -1026,7 +1026,22 @@ function zodDefToJsonSchema(def) {
|
|
|
1026
1026
|
return result;
|
|
1027
1027
|
}
|
|
1028
1028
|
if (type === "literal") {
|
|
1029
|
-
|
|
1029
|
+
const values = Array.isArray(def.values)
|
|
1030
|
+
? def.values
|
|
1031
|
+
: def.values instanceof Set
|
|
1032
|
+
? [...def.values]
|
|
1033
|
+
: [def.value];
|
|
1034
|
+
const jsonTypeOf = (value) => value === null ? "null" : typeof value;
|
|
1035
|
+
const uniqueTypes = [...new Set(values.map(jsonTypeOf))];
|
|
1036
|
+
if (uniqueTypes.length === 1) {
|
|
1037
|
+
return { type: uniqueTypes[0], enum: values };
|
|
1038
|
+
}
|
|
1039
|
+
return {
|
|
1040
|
+
anyOf: values.map((value) => ({
|
|
1041
|
+
type: jsonTypeOf(value),
|
|
1042
|
+
enum: [value],
|
|
1043
|
+
})),
|
|
1044
|
+
};
|
|
1030
1045
|
}
|
|
1031
1046
|
if (type === "array") {
|
|
1032
1047
|
const result = { type: "array" };
|
|
@@ -631,6 +631,7 @@ export interface ExecuteAgentToolCallOptions {
|
|
|
631
631
|
onApprovalRequired?: (binding: AgentApprovalBinding) => Promise<string | void>;
|
|
632
632
|
consumeApproval?: (binding: AgentApprovalBinding) => Promise<boolean>;
|
|
633
633
|
isToolAlwaysAllowed?: (binding: AgentApprovalBinding) => Promise<boolean>;
|
|
634
|
+
beforeWrite?: () => Promise<void>;
|
|
634
635
|
send?: (event: AgentChatEvent) => void;
|
|
635
636
|
}
|
|
636
637
|
export interface AgentApprovalBinding {
|
|
@@ -3427,6 +3427,7 @@ export async function executeAgentToolCall(options) {
|
|
|
3427
3427
|
onApprovalRequired: options.onApprovalRequired,
|
|
3428
3428
|
consumeApproval: options.consumeApproval,
|
|
3429
3429
|
isToolAlwaysAllowed: options.isToolAlwaysAllowed,
|
|
3430
|
+
beforeWrite: options.beforeWrite,
|
|
3430
3431
|
});
|
|
3431
3432
|
}
|
|
3432
3433
|
catch (error) {
|
|
@@ -11050,6 +11051,7 @@ export function createProductionAgentHandler(options) {
|
|
|
11050
11051
|
onApprovalRequired: approvalHooks.onApprovalRequired,
|
|
11051
11052
|
consumeApproval: approvalHooks.consumeApproval,
|
|
11052
11053
|
isToolAlwaysAllowed: approvalHooks.isToolAlwaysAllowed,
|
|
11054
|
+
beforeWrite: agentLoopOpts.beforeWrite,
|
|
11053
11055
|
send,
|
|
11054
11056
|
});
|
|
11055
11057
|
if (approvedExecution.status === "approval_required" ||
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getThread, updateThreadData, withThreadDataLock, } from "../chat-threads/store.js";
|
|
2
|
+
import { RUN_NOT_STARTED_METADATA_KEY } from "../shared/agent-chat-run-not-started.js";
|
|
2
3
|
import { readStoppedRunForThreadFold } from "./run-store.js";
|
|
3
4
|
import { buildAssistantMessage, extractThreadMeta, foldAssistantTurn, isRunPrompt, normalizeThreadRepository, } from "./thread-data-builder.js";
|
|
4
5
|
/**
|
|
@@ -16,7 +17,7 @@ import { buildAssistantMessage, extractThreadMeta, foldAssistantTurn, isRunPromp
|
|
|
16
17
|
export async function foldReapedRunIntoThread(runId, terminalError) {
|
|
17
18
|
const run = await readStoppedRunForThreadFold(runId);
|
|
18
19
|
if (!run || run.laterTurnStarted)
|
|
19
|
-
return;
|
|
20
|
+
return "superseded";
|
|
20
21
|
const events = [...run.events];
|
|
21
22
|
if (terminalError) {
|
|
22
23
|
const last = events.at(-1);
|
|
@@ -45,18 +46,19 @@ export async function foldReapedRunIntoThread(runId, terminalError) {
|
|
|
45
46
|
suppressInternalContinuation: run.continuedInTurn,
|
|
46
47
|
});
|
|
47
48
|
if (!assistant)
|
|
48
|
-
return;
|
|
49
|
-
|
|
49
|
+
return "projected";
|
|
50
|
+
return withThreadDataLock(run.threadId, async () => {
|
|
50
51
|
const thread = await getThread(run.threadId);
|
|
51
52
|
if (!thread)
|
|
52
|
-
return;
|
|
53
|
+
return "superseded";
|
|
53
54
|
const repo = normalizeThreadRepository(JSON.parse(thread.threadData || "{}"));
|
|
54
55
|
const folded = foldAssistantTurn(repo, assistant, {
|
|
55
56
|
runId,
|
|
56
57
|
turnId: run.turnId,
|
|
57
58
|
});
|
|
58
59
|
const meta = extractThreadMeta(folded);
|
|
59
|
-
|
|
60
|
+
let rejectedDisposition = "superseded";
|
|
61
|
+
const updated = await updateThreadData(run.threadId, JSON.stringify(folded), thread.title, meta.preview || thread.preview, folded.messages.length, {
|
|
60
62
|
beforeUpdate: async (tx) => {
|
|
61
63
|
await tx.execute({
|
|
62
64
|
sql: "SELECT pg_advisory_xact_lock(hashtextextended(?, 0::bigint))",
|
|
@@ -88,11 +90,24 @@ export async function foldReapedRunIntoThread(runId, terminalError) {
|
|
|
88
90
|
return false;
|
|
89
91
|
const currentRepo = normalizeThreadRepository(JSON.parse(currentThreadData || "{}"));
|
|
90
92
|
const last = currentRepo.messages.at(-1)?.message ?? currentRepo.messages.at(-1);
|
|
91
|
-
|
|
93
|
+
const ownsPrompt = last?.role === "user"
|
|
92
94
|
? isRunPrompt(last, { runId, ...run })
|
|
93
95
|
: last?.role === "assistant" &&
|
|
94
96
|
last.metadata?.custom?.turnId === run.turnId;
|
|
97
|
+
const unstartedTurnId = last?.role === "user"
|
|
98
|
+
? last.metadata?.custom?.submittedTurnId
|
|
99
|
+
: last?.role === "assistant"
|
|
100
|
+
? last.metadata?.custom?.turnId
|
|
101
|
+
: undefined;
|
|
102
|
+
const isRefusedNewerTurn = last?.metadata?.custom?.[RUN_NOT_STARTED_METADATA_KEY] === true &&
|
|
103
|
+
typeof unstartedTurnId === "string" &&
|
|
104
|
+
unstartedTurnId !== run.turnId;
|
|
105
|
+
if (!ownsPrompt && !isRefusedNewerTurn) {
|
|
106
|
+
rejectedDisposition = "retry";
|
|
107
|
+
}
|
|
108
|
+
return ownsPrompt;
|
|
95
109
|
},
|
|
96
110
|
});
|
|
111
|
+
return updated ? "projected" : rejectedDisposition;
|
|
97
112
|
});
|
|
98
113
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { writeSync } from "node:fs";
|
|
2
2
|
import postgres from "postgres";
|
|
3
|
+
import { ensureChatThreadTables, getThread, markQueuedSubmissionSubmitted, updateThreadData, } from "../chat-threads/store.js";
|
|
3
4
|
import { toPostgresParams, withDbExec } from "../db/client.js";
|
|
4
5
|
import { withMigrationRuntime } from "../db/migration-runtime.js";
|
|
5
6
|
import { withExecutionFence } from "./execution-fence.js";
|
|
6
7
|
import { beginAgentExecutionStep, claimTurnExecutionLease, completeAgentExecutionStep, ensureRunTables, insertRun, insertRunEvent, markAgentExecutionStepDispatched, } from "./run-store.js";
|
|
8
|
+
import { applySubmittedUserMessage, buildUserMessage, extractThreadMeta, } from "./thread-data-builder.js";
|
|
7
9
|
function executor(sql) {
|
|
8
10
|
return {
|
|
9
11
|
async execute(statement) {
|
|
@@ -54,6 +56,62 @@ async function main() {
|
|
|
54
56
|
process.stdout.write(JSON.stringify({ status: "unexpected-write" }));
|
|
55
57
|
return;
|
|
56
58
|
}
|
|
59
|
+
if (mode === "queue-submit") {
|
|
60
|
+
await withMigrationRuntime(() => ensureChatThreadTables());
|
|
61
|
+
const input = JSON.parse(extra ?? "null");
|
|
62
|
+
const thread = await getThread(threadId);
|
|
63
|
+
if (!thread)
|
|
64
|
+
throw new Error(`Missing queued-message thread ${threadId}`);
|
|
65
|
+
const userMessage = buildUserMessage({
|
|
66
|
+
text: input.message.text,
|
|
67
|
+
runId,
|
|
68
|
+
queuedMessageId: input.message.id,
|
|
69
|
+
});
|
|
70
|
+
let submissionFailure;
|
|
71
|
+
let pauseBeforeWrite = true;
|
|
72
|
+
const updated = await updateThreadData(threadId, "{}", thread.title, thread.preview, thread.messageCount, {
|
|
73
|
+
transformThreadData: (threadData) => {
|
|
74
|
+
const result = applySubmittedUserMessage(JSON.parse(threadData || "{}"), userMessage, {
|
|
75
|
+
id: input.message.id,
|
|
76
|
+
claimId: input.claimId,
|
|
77
|
+
now: Date.now(),
|
|
78
|
+
});
|
|
79
|
+
if (!("repo" in result)) {
|
|
80
|
+
submissionFailure = result.status;
|
|
81
|
+
return threadData;
|
|
82
|
+
}
|
|
83
|
+
const meta = extractThreadMeta(result.repo);
|
|
84
|
+
return {
|
|
85
|
+
threadData: JSON.stringify(result.repo),
|
|
86
|
+
preview: meta.preview || thread.preview,
|
|
87
|
+
};
|
|
88
|
+
},
|
|
89
|
+
beforeUpdate: async () => {
|
|
90
|
+
if (pauseBeforeWrite) {
|
|
91
|
+
pauseBeforeWrite = false;
|
|
92
|
+
writeSync(1, `${JSON.stringify({ ready: true })}\n`);
|
|
93
|
+
await waitForContinue();
|
|
94
|
+
}
|
|
95
|
+
return !submissionFailure;
|
|
96
|
+
},
|
|
97
|
+
afterUpdate: async (tx) => {
|
|
98
|
+
if (submissionFailure)
|
|
99
|
+
return;
|
|
100
|
+
await markQueuedSubmissionSubmitted(tx, {
|
|
101
|
+
threadId,
|
|
102
|
+
messageId: input.message.id,
|
|
103
|
+
runId,
|
|
104
|
+
actor: input.actor,
|
|
105
|
+
message: input.message,
|
|
106
|
+
});
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
writeSync(1, JSON.stringify({
|
|
110
|
+
status: submissionFailure ?? "submitted",
|
|
111
|
+
updated,
|
|
112
|
+
}));
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
57
115
|
await insertRun(runId, threadId, turnId);
|
|
58
116
|
const fence = await claimTurnExecutionLease(threadId, turnId, runId);
|
|
59
117
|
if (mode === "claim") {
|
package/dist/agent/run-store.js
CHANGED
|
@@ -2123,10 +2123,10 @@ async function reapSingleStaleRun(runId, source, maxStaleMs) {
|
|
|
2123
2123
|
args: [runScope.thread_id, runScope.turn_id, runId, lease.generation],
|
|
2124
2124
|
});
|
|
2125
2125
|
}
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2126
|
+
const executionLeaseChanged = !generationMatches && (lease !== null || run.worker_generation != null);
|
|
2127
|
+
outcome = executionLeaseChanged
|
|
2128
|
+
? { outcome: "execution_lease_changed" }
|
|
2129
|
+
: await attemptStaleRunRecovery(tx, runId);
|
|
2130
2130
|
await appendTerminalRunEventInTransaction(tx, runId, STALE_RUN_ERROR_EVENT);
|
|
2131
2131
|
if (outcome.outcome !== "execution_lease_changed") {
|
|
2132
2132
|
const projection = await tx.execute({
|
|
@@ -3555,7 +3555,9 @@ async function finalizeStaleRun(runId, source, projectThread = true) {
|
|
|
3555
3555
|
return true;
|
|
3556
3556
|
try {
|
|
3557
3557
|
const { foldReapedRunIntoThread } = await import("./reaped-run-thread.js");
|
|
3558
|
-
await foldReapedRunIntoThread(runId);
|
|
3558
|
+
const disposition = await foldReapedRunIntoThread(runId);
|
|
3559
|
+
if (disposition === "retry")
|
|
3560
|
+
return false;
|
|
3559
3561
|
await getDbExec().execute({
|
|
3560
3562
|
sql: `UPDATE agent_runs
|
|
3561
3563
|
SET thread_projection_pending = FALSE
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { buildAssistantMessage } from "./thread-data-builder.js";
|
|
2
|
+
import type { RunEvent } from "./types.js";
|
|
3
|
+
interface BuildJournalAssistantMessageOptions {
|
|
4
|
+
suppressInternalContinuation?: boolean;
|
|
5
|
+
preserveUnknownToolOutcomes?: boolean;
|
|
6
|
+
scope?: {
|
|
7
|
+
type: string;
|
|
8
|
+
id: string;
|
|
9
|
+
} | null;
|
|
10
|
+
runDurationMs?: number;
|
|
11
|
+
}
|
|
12
|
+
export declare class RunTranscriptJournalGapError extends Error {
|
|
13
|
+
readonly runId: string;
|
|
14
|
+
readonly missingSequences: number[];
|
|
15
|
+
constructor(runId: string, missingSequences: number[]);
|
|
16
|
+
}
|
|
17
|
+
/** Builds assistant content from the durable event journal and verifies that
|
|
18
|
+
* every published nonterminal event reached that journal before projection.
|
|
19
|
+
*/
|
|
20
|
+
export declare function buildAssistantMessageFromRunJournal(input: {
|
|
21
|
+
threadId: string;
|
|
22
|
+
turnId: string;
|
|
23
|
+
runId: string;
|
|
24
|
+
runEvents: readonly RunEvent[];
|
|
25
|
+
options?: BuildJournalAssistantMessageOptions;
|
|
26
|
+
}): Promise<ReturnType<typeof buildAssistantMessage>>;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { CHECKPOINT_TERMINAL_EVENT_SEQ, getCurrentTurnRunEventsForThread, } from "./run-store.js";
|
|
2
|
+
import { buildAssistantMessage, foldAssistantTurn, } from "./thread-data-builder.js";
|
|
3
|
+
export class RunTranscriptJournalGapError extends Error {
|
|
4
|
+
runId;
|
|
5
|
+
missingSequences;
|
|
6
|
+
constructor(runId, missingSequences) {
|
|
7
|
+
super(`Run transcript journal is missing events for ${runId}: ${missingSequences.join(", ")}`);
|
|
8
|
+
this.runId = runId;
|
|
9
|
+
this.missingSequences = missingSequences;
|
|
10
|
+
this.name = "RunTranscriptJournalGapError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function isTerminalProjectionEvent(event) {
|
|
14
|
+
return (event.type === "done" ||
|
|
15
|
+
event.type === "error" ||
|
|
16
|
+
event.type === "missing_api_key" ||
|
|
17
|
+
event.type === "loop_limit" ||
|
|
18
|
+
event.type === "auto_continue");
|
|
19
|
+
}
|
|
20
|
+
/** Builds assistant content from the durable event journal and verifies that
|
|
21
|
+
* every published nonterminal event reached that journal before projection.
|
|
22
|
+
*/
|
|
23
|
+
export async function buildAssistantMessageFromRunJournal(input) {
|
|
24
|
+
const turnJournal = await getCurrentTurnRunEventsForThread(input.threadId, input.turnId);
|
|
25
|
+
const currentRunPersisted = turnJournal.filter((event) => event.runId === input.runId && event.seq < CHECKPOINT_TERMINAL_EVENT_SEQ);
|
|
26
|
+
const persistedSequences = new Set(currentRunPersisted.map(({ seq }) => seq));
|
|
27
|
+
const missingSequences = input.runEvents
|
|
28
|
+
.filter(({ seq, event }) => !isTerminalProjectionEvent(event) && !persistedSequences.has(seq))
|
|
29
|
+
.map(({ seq }) => seq);
|
|
30
|
+
if (missingSequences.length > 0) {
|
|
31
|
+
throw new RunTranscriptJournalGapError(input.runId, missingSequences);
|
|
32
|
+
}
|
|
33
|
+
const currentRunEndIndex = turnJournal.reduce((lastIndex, { runId }, index) => runId === input.runId ? index : lastIndex, -1);
|
|
34
|
+
const relevantJournal = currentRunEndIndex === -1
|
|
35
|
+
? []
|
|
36
|
+
: turnJournal
|
|
37
|
+
.slice(0, currentRunEndIndex + 1)
|
|
38
|
+
.filter(({ runId, seq }) => runId !== input.runId || seq < CHECKPOINT_TERMINAL_EVENT_SEQ);
|
|
39
|
+
const runGroups = new Map();
|
|
40
|
+
for (const { runId, seq, event } of relevantJournal) {
|
|
41
|
+
const events = runGroups.get(runId) ?? [];
|
|
42
|
+
events.push({ seq, event });
|
|
43
|
+
runGroups.set(runId, events);
|
|
44
|
+
}
|
|
45
|
+
if (!runGroups.has(input.runId))
|
|
46
|
+
runGroups.set(input.runId, []);
|
|
47
|
+
let repo = { messages: [] };
|
|
48
|
+
for (const [runId, persistedRunEvents] of runGroups) {
|
|
49
|
+
const runEvents = persistedRunEvents.sort((left, right) => left.seq - right.seq);
|
|
50
|
+
if (runId === input.runId) {
|
|
51
|
+
for (const event of input.runEvents) {
|
|
52
|
+
if (isTerminalProjectionEvent(event.event) &&
|
|
53
|
+
!persistedSequences.has(event.seq)) {
|
|
54
|
+
runEvents.push(event);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
runEvents.sort((left, right) => left.seq - right.seq);
|
|
58
|
+
}
|
|
59
|
+
const assistant = buildAssistantMessage(runEvents, runId, runId === input.runId
|
|
60
|
+
? { ...input.options, turnId: input.turnId }
|
|
61
|
+
: {
|
|
62
|
+
suppressInternalContinuation: input.options?.suppressInternalContinuation,
|
|
63
|
+
preserveUnknownToolOutcomes: input.options?.preserveUnknownToolOutcomes,
|
|
64
|
+
scope: input.options?.scope,
|
|
65
|
+
turnId: input.turnId,
|
|
66
|
+
});
|
|
67
|
+
if (!assistant)
|
|
68
|
+
continue;
|
|
69
|
+
repo = foldAssistantTurn(repo, assistant, {
|
|
70
|
+
runId,
|
|
71
|
+
turnId: input.turnId,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
const entry = repo.messages.at(-1);
|
|
75
|
+
const message = entry && typeof entry === "object" && "message" in entry
|
|
76
|
+
? entry.message
|
|
77
|
+
: entry;
|
|
78
|
+
return (message ?? null);
|
|
79
|
+
}
|
|
@@ -3415,7 +3415,7 @@ function assistantContentWeight(content) {
|
|
|
3415
3415
|
}
|
|
3416
3416
|
return weight;
|
|
3417
3417
|
}
|
|
3418
|
-
function appendFoldedContent(existing, incoming) {
|
|
3418
|
+
function appendFoldedContent(existing, incoming, separateRunBoundary = false) {
|
|
3419
3419
|
const merged = existing.map((p) => ({ ...p }));
|
|
3420
3420
|
for (const part of incoming) {
|
|
3421
3421
|
if (part?.type === "tool-call" && part.result !== undefined) {
|
|
@@ -3441,11 +3441,12 @@ function appendFoldedContent(existing, incoming) {
|
|
|
3441
3441
|
typeof part.text === "string" &&
|
|
3442
3442
|
last?.type === "text" &&
|
|
3443
3443
|
typeof last.text === "string") {
|
|
3444
|
-
last.text = `${last.text}${part.text}`;
|
|
3444
|
+
last.text = `${last.text}${separateRunBoundary ? "\n\n" : ""}${part.text}`;
|
|
3445
3445
|
}
|
|
3446
3446
|
else {
|
|
3447
3447
|
merged.push({ ...part });
|
|
3448
3448
|
}
|
|
3449
|
+
separateRunBoundary = false;
|
|
3449
3450
|
}
|
|
3450
3451
|
return normalizeAssistantToolCallIds({
|
|
3451
3452
|
role: "assistant",
|
|
@@ -3476,15 +3477,34 @@ export function foldAssistantTurn(repo, assistantMsg, options) {
|
|
|
3476
3477
|
? assistantMsg.content
|
|
3477
3478
|
: [];
|
|
3478
3479
|
const existingFolded = foldedRunIdsOf(lastMsg);
|
|
3480
|
+
const incomingFolded = foldedRunIdsOf(assistantMsg);
|
|
3479
3481
|
const runAlreadyFolded = !!runId &&
|
|
3480
3482
|
(existingFolded.includes(runId) || getMessageRunId(lastMsg) === runId);
|
|
3481
|
-
const
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3483
|
+
const supersededRunReplay = runAlreadyFolded && !!runId && getMessageRunId(lastMsg) !== runId;
|
|
3484
|
+
const incomingCoversExistingRuns = existingFolded.length > 0 &&
|
|
3485
|
+
!!runId &&
|
|
3486
|
+
incomingFolded.includes(runId) &&
|
|
3487
|
+
existingFolded.every((foldedRunId) => incomingFolded.includes(foldedRunId));
|
|
3488
|
+
let mergedContent = existingContent;
|
|
3489
|
+
if (!supersededRunReplay) {
|
|
3490
|
+
if (incomingCoversExistingRuns) {
|
|
3491
|
+
mergedContent = incomingContent;
|
|
3492
|
+
}
|
|
3493
|
+
else if (runAlreadyFolded) {
|
|
3494
|
+
if (assistantContentWeight(incomingContent) >
|
|
3495
|
+
assistantContentWeight(existingContent)) {
|
|
3496
|
+
mergedContent = incomingContent;
|
|
3497
|
+
}
|
|
3498
|
+
}
|
|
3499
|
+
else {
|
|
3500
|
+
mergedContent = appendFoldedContent(existingContent, incomingContent, lastMsg.status?.type === "incomplete");
|
|
3501
|
+
}
|
|
3502
|
+
}
|
|
3503
|
+
const mergedFolded = Array.from(new Set([
|
|
3504
|
+
...existingFolded,
|
|
3505
|
+
...(supersededRunReplay ? [] : incomingFolded),
|
|
3506
|
+
...(runId ? [runId] : []),
|
|
3507
|
+
]));
|
|
3488
3508
|
const existingCustom = lastMsg.metadata?.custom && typeof lastMsg.metadata.custom === "object"
|
|
3489
3509
|
? lastMsg.metadata.custom
|
|
3490
3510
|
: {};
|
|
@@ -3494,7 +3514,7 @@ export function foldAssistantTurn(repo, assistantMsg, options) {
|
|
|
3494
3514
|
: {};
|
|
3495
3515
|
const mergedCustom = {
|
|
3496
3516
|
...existingCustom,
|
|
3497
|
-
...incomingCustom,
|
|
3517
|
+
...(supersededRunReplay ? {} : incomingCustom),
|
|
3498
3518
|
turnId,
|
|
3499
3519
|
foldedRunIds: mergedFolded,
|
|
3500
3520
|
};
|
|
@@ -3512,8 +3532,9 @@ export function foldAssistantTurn(repo, assistantMsg, options) {
|
|
|
3512
3532
|
if (mergedDurationMs != null) {
|
|
3513
3533
|
mergedCustom[ASSISTANT_RUN_DURATION_METADATA_KEY] = mergedDurationMs;
|
|
3514
3534
|
}
|
|
3515
|
-
if (incomingCustom.continued !== true)
|
|
3535
|
+
if (!supersededRunReplay && incomingCustom.continued !== true) {
|
|
3516
3536
|
delete mergedCustom.continued;
|
|
3537
|
+
}
|
|
3517
3538
|
// A turn's failure is its newest run's: a run that continued past an
|
|
3518
3539
|
// earlier stop clears the stop's error instead of inheriting it.
|
|
3519
3540
|
if (!runAlreadyFolded && incomingCustom.runError === undefined) {
|
|
@@ -3525,10 +3546,14 @@ export function foldAssistantTurn(repo, assistantMsg, options) {
|
|
|
3525
3546
|
role: "assistant",
|
|
3526
3547
|
content: mergedContent,
|
|
3527
3548
|
}).content,
|
|
3528
|
-
status:
|
|
3549
|
+
status: supersededRunReplay
|
|
3550
|
+
? lastMsg.status
|
|
3551
|
+
: (assistantMsg.status ?? lastMsg.status),
|
|
3529
3552
|
metadata: {
|
|
3530
3553
|
...lastMsg.metadata,
|
|
3531
|
-
runId:
|
|
3554
|
+
runId: supersededRunReplay
|
|
3555
|
+
? lastMsg.metadata?.runId
|
|
3556
|
+
: (runId ?? lastMsg.metadata?.runId),
|
|
3532
3557
|
custom: mergedCustom,
|
|
3533
3558
|
},
|
|
3534
3559
|
};
|
|
@@ -139,8 +139,8 @@ export declare function createAutomationCallbackHandler(runtime: AutomationRunti
|
|
|
139
139
|
duplicate?: undefined;
|
|
140
140
|
eventId?: undefined;
|
|
141
141
|
} | {
|
|
142
|
-
error?: undefined;
|
|
143
142
|
accepted: boolean;
|
|
144
143
|
duplicate: boolean;
|
|
145
144
|
eventId: string;
|
|
145
|
+
error?: undefined;
|
|
146
146
|
}>;
|
package/dist/cli/create.d.ts
CHANGED
|
@@ -183,5 +183,5 @@ declare function replacePlaceholders(dir: string, appName: string, appTitle: str
|
|
|
183
183
|
declare function copyDir(src: string, dest: string, root?: string, opts?: {
|
|
184
184
|
skipExisting?: boolean;
|
|
185
185
|
materializeSymlinks?: boolean;
|
|
186
|
-
}): void;
|
|
186
|
+
}, destinationRoot?: string): void;
|
|
187
187
|
declare function shouldSkipScaffoldEntry(name: string, srcPath?: string): boolean;
|
package/dist/cli/create.js
CHANGED
|
@@ -5,7 +5,8 @@ import path from "path";
|
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from "url";
|
|
6
6
|
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
7
7
|
import { DISPATCH_WORKSPACE_ROOT_REDIRECTS, getWorkspaceAppIdValidationError, } from "../shared/workspace-app-id.js";
|
|
8
|
-
import { setupAgentSymlinks } from "./setup-agents.js";
|
|
8
|
+
import { createAgentSymlink, prepareAgentSymlink, rebaseAgentSymlinkTarget, setupAgentSymlinks, } from "./setup-agents.js";
|
|
9
|
+
import { resolveTarExecutable } from "./tar-command.js";
|
|
9
10
|
import { applyTemplateLayer, readTemplateLayer } from "./template-layer.js";
|
|
10
11
|
import { coreTemplates, getTemplate, allTemplateNames, } from "./templates-meta.js";
|
|
11
12
|
import { addConfiguredMigrationDependencies, readUpgradeEnvironment, } from "./upgrade.js";
|
|
@@ -674,22 +675,11 @@ function linkWorkspaceRootSkills(targetDir) {
|
|
|
674
675
|
const linkPath = path.join(agentsDir, "skills");
|
|
675
676
|
const target = "../packages/shared/.agents/skills";
|
|
676
677
|
fs.mkdirSync(agentsDir, { recursive: true });
|
|
678
|
+
const state = prepareAgentSymlink(linkPath, target, "dir");
|
|
679
|
+
if (state !== "create")
|
|
680
|
+
return;
|
|
677
681
|
try {
|
|
678
|
-
|
|
679
|
-
if (stat.isSymbolicLink()) {
|
|
680
|
-
if (fs.readlinkSync(linkPath) === target)
|
|
681
|
-
return;
|
|
682
|
-
fs.unlinkSync(linkPath);
|
|
683
|
-
}
|
|
684
|
-
else {
|
|
685
|
-
return;
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
catch {
|
|
689
|
-
// Missing link; create below.
|
|
690
|
-
}
|
|
691
|
-
try {
|
|
692
|
-
fs.symlinkSync(target, linkPath, process.platform === "win32" ? "junction" : "dir");
|
|
682
|
+
createAgentSymlink(target, linkPath, "dir");
|
|
693
683
|
}
|
|
694
684
|
catch {
|
|
695
685
|
try {
|
|
@@ -1338,7 +1328,7 @@ function localPackageTarball(packageDir) {
|
|
|
1338
1328
|
}
|
|
1339
1329
|
const rawTarball = path.join(packDir, tarballs[0]);
|
|
1340
1330
|
const unpackDir = fs.mkdtempSync(path.join(os.tmpdir(), "agent-native-local-package-unpack-"));
|
|
1341
|
-
execFileSync(
|
|
1331
|
+
execFileSync(resolveTarExecutable(), ["-xzf", rawTarball, "-C", unpackDir], {
|
|
1342
1332
|
stdio: "pipe",
|
|
1343
1333
|
});
|
|
1344
1334
|
rewritePublishedPackageManifest(path.join(unpackDir, "package", "package.json"));
|
|
@@ -1948,7 +1938,7 @@ function tarExtractArgs(tarPath, destDir, options = {}) {
|
|
|
1948
1938
|
];
|
|
1949
1939
|
}
|
|
1950
1940
|
function archiveSymlinksForExtraction(tarPath) {
|
|
1951
|
-
const listing = execFileSync(
|
|
1941
|
+
const listing = execFileSync(resolveTarExecutable(), ["tvzf", tarPath], {
|
|
1952
1942
|
encoding: "utf-8",
|
|
1953
1943
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1954
1944
|
maxBuffer: TAR_LISTING_MAX_BUFFER,
|
|
@@ -2037,7 +2027,7 @@ function extractTarball(tarPath, destDir, options = {}) {
|
|
|
2037
2027
|
const symlinks = options.skipAgentSymlinks || options.untrustedCommunityArchive
|
|
2038
2028
|
? archiveSymlinksForExtraction(tarPath)
|
|
2039
2029
|
: [];
|
|
2040
|
-
execFileSync(
|
|
2030
|
+
execFileSync(resolveTarExecutable(), tarExtractArgs(tarPath, destDir, {
|
|
2041
2031
|
...options,
|
|
2042
2032
|
additionalExcludes: symlinks.map((link) => link.archivePath),
|
|
2043
2033
|
}), {
|
|
@@ -2071,7 +2061,7 @@ async function downloadAndExtract(url, destDir, options = {}) {
|
|
|
2071
2061
|
}
|
|
2072
2062
|
}
|
|
2073
2063
|
function validateCommunityArchive(tarPath) {
|
|
2074
|
-
const listing = execFileSync(
|
|
2064
|
+
const listing = execFileSync(resolveTarExecutable(), ["tvzf", tarPath], {
|
|
2075
2065
|
encoding: "utf-8",
|
|
2076
2066
|
stdio: ["ignore", "pipe", "pipe"],
|
|
2077
2067
|
maxBuffer: TAR_LISTING_MAX_BUFFER,
|
|
@@ -3451,8 +3441,9 @@ function replacePlaceholders(dir, appName, appTitle, workspaceName) {
|
|
|
3451
3441
|
fs.writeFileSync(p, next);
|
|
3452
3442
|
}
|
|
3453
3443
|
}
|
|
3454
|
-
function copyDir(src, dest, root, opts) {
|
|
3444
|
+
function copyDir(src, dest, root, opts, destinationRoot) {
|
|
3455
3445
|
const resolvedRoot = root ?? path.resolve(src);
|
|
3446
|
+
const resolvedDestinationRoot = destinationRoot ?? path.resolve(dest);
|
|
3456
3447
|
const skipExisting = opts?.skipExisting ?? false;
|
|
3457
3448
|
fs.mkdirSync(dest, { recursive: true });
|
|
3458
3449
|
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
@@ -3465,16 +3456,28 @@ function copyDir(src, dest, root, opts) {
|
|
|
3465
3456
|
}
|
|
3466
3457
|
if (entry.isSymbolicLink()) {
|
|
3467
3458
|
const target = fs.readlinkSync(srcPath);
|
|
3468
|
-
const
|
|
3469
|
-
if (!opts?.materializeSymlinks &&
|
|
3470
|
-
|
|
3471
|
-
|
|
3459
|
+
const copiedTarget = rebaseAgentSymlinkTarget(resolvedRoot, resolvedDestinationRoot, srcPath, destPath, target);
|
|
3460
|
+
if (!opts?.materializeSymlinks && copiedTarget !== undefined) {
|
|
3461
|
+
let stat;
|
|
3462
|
+
try {
|
|
3463
|
+
stat = fs.statSync(srcPath);
|
|
3464
|
+
}
|
|
3465
|
+
catch (error) {
|
|
3466
|
+
if (error.code !== "ENOENT")
|
|
3467
|
+
throw error;
|
|
3468
|
+
if (process.platform === "win32") {
|
|
3469
|
+
throw new Error(`Cannot copy dangling symlink at "${srcPath}" on Windows because its target type is unknown.`);
|
|
3470
|
+
}
|
|
3471
|
+
fs.symlinkSync(copiedTarget, destPath);
|
|
3472
|
+
continue;
|
|
3473
|
+
}
|
|
3474
|
+
createAgentSymlink(copiedTarget, destPath, stat.isDirectory() ? "dir" : "file");
|
|
3472
3475
|
}
|
|
3473
3476
|
else {
|
|
3474
3477
|
try {
|
|
3475
3478
|
const stat = fs.statSync(srcPath);
|
|
3476
3479
|
if (stat.isDirectory()) {
|
|
3477
|
-
copyDir(srcPath, destPath, resolvedRoot, opts);
|
|
3480
|
+
copyDir(srcPath, destPath, resolvedRoot, opts, resolvedDestinationRoot);
|
|
3478
3481
|
}
|
|
3479
3482
|
else {
|
|
3480
3483
|
fs.copyFileSync(srcPath, destPath);
|
|
@@ -3486,7 +3489,7 @@ function copyDir(src, dest, root, opts) {
|
|
|
3486
3489
|
}
|
|
3487
3490
|
}
|
|
3488
3491
|
else if (entry.isDirectory()) {
|
|
3489
|
-
copyDir(srcPath, destPath, resolvedRoot, opts);
|
|
3492
|
+
copyDir(srcPath, destPath, resolvedRoot, opts, resolvedDestinationRoot);
|
|
3490
3493
|
}
|
|
3491
3494
|
else {
|
|
3492
3495
|
fs.copyFileSync(srcPath, destPath);
|
|
@@ -3,6 +3,7 @@ import fs from "node:fs";
|
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { assertAgentNativePackageManifest, } from "../package-lifecycle/manifest.js";
|
|
6
|
+
import { resolveTarExecutable } from "./tar-command.js";
|
|
6
7
|
const defaultIO = {
|
|
7
8
|
out: (message) => console.log(message),
|
|
8
9
|
err: (message) => console.error(message),
|
|
@@ -96,7 +97,7 @@ export async function loadLifecyclePackage(name, cwd, spawn = spawnSync) {
|
|
|
96
97
|
throw new Error(`npm pack returned an invalid archive name for '${name}'`);
|
|
97
98
|
}
|
|
98
99
|
const archive = path.join(stage, filename);
|
|
99
|
-
const listed = spawn(
|
|
100
|
+
const listed = spawn(resolveTarExecutable(), ["-tzf", archive], {
|
|
100
101
|
cwd,
|
|
101
102
|
encoding: "utf8",
|
|
102
103
|
stdio: "pipe",
|
|
@@ -114,7 +115,7 @@ export async function loadLifecyclePackage(name, cwd, spawn = spawnSync) {
|
|
|
114
115
|
entry.split("/").includes(".."))) {
|
|
115
116
|
throw new Error(`Package archive for '${name}' contains an unsafe path`);
|
|
116
117
|
}
|
|
117
|
-
const extracted = spawn(
|
|
118
|
+
const extracted = spawn(resolveTarExecutable(), [
|
|
118
119
|
"-xzf",
|
|
119
120
|
archive,
|
|
120
121
|
"-C",
|
|
@@ -1,2 +1,7 @@
|
|
|
1
|
+
type SymlinkState = "create" | "matches" | "occupied";
|
|
2
|
+
export declare function prepareAgentSymlink(linkPath: string, target: string, type: "file" | "dir"): SymlinkState;
|
|
3
|
+
export declare function createAgentSymlink(target: string, linkPath: string, type: "file" | "dir"): void;
|
|
4
|
+
export declare function rebaseAgentSymlinkTarget(sourceRoot: string, destinationRoot: string, sourceLinkPath: string, destinationLinkPath: string, target: string): string | undefined;
|
|
1
5
|
export declare function setupAgentSymlinks(targetDir: string): void;
|
|
2
6
|
export declare function runSetupAgents(): void;
|
|
7
|
+
export {};
|