@circuitwall/jarela 0.7.2 → 0.7.3
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/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +2 -2
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +2 -2
- package/.next/standalone/.next/server/app/_not-found.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/v1/agents/[id]/compact/route.js +51 -35
- package/.next/standalone/.next/server/app/api/v1/agents/[id]/compact/route.js.map +1 -1
- package/.next/standalone/.next/server/app/api/v1/threads/[thread_id]/run/route.js +2 -2
- package/.next/standalone/.next/server/app/index.html +2 -2
- package/.next/standalone/.next/server/app/index.rsc +3 -3
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page.js +515 -104
- package/.next/standalone/.next/server/app/page.js.map +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/setup.html +1 -1
- package/.next/standalone/.next/server/app/setup.rsc +2 -2
- package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/setup.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/setup.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/setup.segments/setup.segment.rsc +1 -1
- package/.next/standalone/.next/server/chunks/1683.js +26 -16
- package/.next/standalone/.next/server/chunks/1683.js.map +1 -1
- package/.next/standalone/.next/server/chunks/{317.js → 5432.js} +11100 -10858
- package/.next/standalone/.next/server/chunks/5432.js.map +1 -0
- package/.next/standalone/.next/server/chunks/7885.js +606 -353
- package/.next/standalone/.next/server/chunks/7885.js.map +1 -1
- package/.next/standalone/.next/server/chunks/8135.js +59 -16
- package/.next/standalone/.next/server/chunks/8135.js.map +1 -1
- package/.next/standalone/.next/server/chunks/9032.js +3 -3
- package/.next/standalone/.next/server/chunks/9032.js.map +1 -1
- package/.next/standalone/.next/server/instrumentation.js +3 -3
- package/.next/standalone/.next/server/instrumentation.js.map +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +2 -2
- package/.next/standalone/.next/server/pages/404.html +2 -2
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/static/chunks/app/{page-a20902703c0a4f10.js → page-9fb006074fb13526.js} +582 -171
- package/.next/standalone/.next/static/chunks/app/page-9fb006074fb13526.js.map +1 -0
- package/.next/standalone/.next/static/css/5507dbe1cdc6c599.css +5 -0
- package/.next/standalone/.next/static/css/5507dbe1cdc6c599.css.map +1 -0
- package/.next/standalone/package.json +1 -1
- package/CHANGELOG.md +11 -0
- package/README.md +83 -1
- package/api/types.ts +7 -0
- package/app/api/v1/agents/[id]/compact/route.ts +2 -40
- package/components/bridges/BridgeEditor.tsx +8 -0
- package/components/chat/MessageBubble.tsx +3 -36
- package/components/models/ModelEditor.tsx +141 -0
- package/components/scheduled-tasks/ScheduledTasksPanel.tsx +5 -0
- package/components/scheduled-tasks/WatchersSection.tsx +5 -0
- package/lib/agents/context-budget.test.ts +128 -0
- package/lib/agents/context-budget.ts +128 -0
- package/lib/agents/conversation-summary.test.ts +68 -0
- package/lib/agents/conversation-summary.ts +51 -0
- package/lib/agents/run-thread.ts +112 -2
- package/lib/bridges/dispatcher.test.ts +134 -0
- package/lib/bridges/dispatcher.ts +34 -16
- package/lib/bridges/message-role.test.ts +83 -0
- package/lib/bridges/message-role.ts +46 -0
- package/lib/triggers/handlers/watcher.test.ts +23 -4
- package/lib/triggers/handlers/watcher.ts +56 -8
- package/package.json +1 -1
- package/.next/standalone/.next/server/chunks/317.js.map +0 -1
- package/.next/standalone/.next/static/chunks/app/page-a20902703c0a4f10.js.map +0 -1
- package/.next/standalone/.next/static/css/cc66c456aba91258.css +0 -5
- package/.next/standalone/.next/static/css/cc66c456aba91258.css.map +0 -1
- /package/.next/standalone/.next/static/{IauO0rNZkUVPX834k-SBa → AbCOWpaxP4v4lUSeFWWYz}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{IauO0rNZkUVPX834k-SBa → AbCOWpaxP4v4lUSeFWWYz}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { formatBridgePrompt, parseBridgePrompt } from "./message-role";
|
|
3
|
+
|
|
4
|
+
describe("bridge prompt envelope", () => {
|
|
5
|
+
it("round-trips DM prompt metadata and body", () => {
|
|
6
|
+
const raw = formatBridgePrompt({
|
|
7
|
+
bridge_id: "b1",
|
|
8
|
+
chat_id: "dm@jid",
|
|
9
|
+
chat_name: "Alice",
|
|
10
|
+
is_group: false,
|
|
11
|
+
role: "counterpart",
|
|
12
|
+
sender_id: "alice@jid",
|
|
13
|
+
sender_name: "Alice",
|
|
14
|
+
text: "hello from dm",
|
|
15
|
+
});
|
|
16
|
+
const parsed = parseBridgePrompt(raw);
|
|
17
|
+
expect(parsed).not.toBeNull();
|
|
18
|
+
expect(parsed?.bridgeId).toBe("b1");
|
|
19
|
+
expect(parsed?.chatJid).toBe("dm@jid");
|
|
20
|
+
expect(parsed?.isGroup).toBe(false);
|
|
21
|
+
expect(parsed?.senderJid).toBe("alice@jid");
|
|
22
|
+
expect(parsed?.body).toBe("hello from dm");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("round-trips group prompt metadata and body", () => {
|
|
26
|
+
const raw = formatBridgePrompt({
|
|
27
|
+
bridge_id: "b1",
|
|
28
|
+
chat_id: "group@jid",
|
|
29
|
+
chat_name: "Family Group",
|
|
30
|
+
is_group: true,
|
|
31
|
+
role: "counterpart",
|
|
32
|
+
sender_id: "bob@jid",
|
|
33
|
+
sender_name: "Bob",
|
|
34
|
+
text: "group message",
|
|
35
|
+
});
|
|
36
|
+
const parsed = parseBridgePrompt(raw);
|
|
37
|
+
expect(parsed).not.toBeNull();
|
|
38
|
+
expect(parsed?.chatJid).toBe("group@jid");
|
|
39
|
+
expect(parsed?.chatName).toBe("Family Group");
|
|
40
|
+
expect(parsed?.isGroup).toBe(true);
|
|
41
|
+
expect(parsed?.senderName).toBe("Bob");
|
|
42
|
+
expect(parsed?.body).toBe("group message");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("parses envelopes with prose preface before bracket headers", () => {
|
|
46
|
+
const raw = [
|
|
47
|
+
"The paired user themselves sent the message below.",
|
|
48
|
+
"",
|
|
49
|
+
"[bridge:b1]",
|
|
50
|
+
"[chat_id:dm@jid]",
|
|
51
|
+
"[chat_name:Alice]",
|
|
52
|
+
"[chat_type:dm]",
|
|
53
|
+
"[sender_id:alice@jid]",
|
|
54
|
+
"[sender_name:Alice]",
|
|
55
|
+
"",
|
|
56
|
+
"body",
|
|
57
|
+
].join("\n");
|
|
58
|
+
const parsed = parseBridgePrompt(raw);
|
|
59
|
+
expect(parsed?.bridgeId).toBe("b1");
|
|
60
|
+
expect(parsed?.body).toBe("body");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("accepts legacy key names for compatibility", () => {
|
|
64
|
+
const raw = [
|
|
65
|
+
"[bridge:b1]",
|
|
66
|
+
"[chat_jid:legacy@jid]",
|
|
67
|
+
"[chat_name:Legacy]",
|
|
68
|
+
"[chat_type:dm]",
|
|
69
|
+
"[sender_jid:sender@jid]",
|
|
70
|
+
"[sender_name:Sender]",
|
|
71
|
+
"",
|
|
72
|
+
"legacy body",
|
|
73
|
+
].join("\n");
|
|
74
|
+
const parsed = parseBridgePrompt(raw);
|
|
75
|
+
expect(parsed?.chatJid).toBe("legacy@jid");
|
|
76
|
+
expect(parsed?.senderJid).toBe("sender@jid");
|
|
77
|
+
expect(parsed?.body).toBe("legacy body");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("returns null when not a bridge envelope", () => {
|
|
81
|
+
expect(parseBridgePrompt("plain text")).toBeNull();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -56,6 +56,18 @@ export interface BridgePromptInput {
|
|
|
56
56
|
text: string;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
// Parsed chat-friendly envelope extracted from a bridge prompt body.
|
|
60
|
+
// Used by the chat UI so rendering stays in lockstep with formatter changes.
|
|
61
|
+
export interface BridgePromptContext {
|
|
62
|
+
bridgeId: string;
|
|
63
|
+
chatJid: string;
|
|
64
|
+
chatName: string;
|
|
65
|
+
isGroup: boolean;
|
|
66
|
+
senderJid: string;
|
|
67
|
+
senderName: string;
|
|
68
|
+
body: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
59
71
|
/**
|
|
60
72
|
* Build the prompt prefix the agent receives for one bridge-inbound message.
|
|
61
73
|
*
|
|
@@ -97,6 +109,40 @@ export function formatBridgePrompt(input: BridgePromptInput): string {
|
|
|
97
109
|
return `${note}\n\n${lines.join("\n")}\n\n${input.text}`;
|
|
98
110
|
}
|
|
99
111
|
|
|
112
|
+
// Parses bridge prompt envelopes rendered by formatBridgePrompt().
|
|
113
|
+
// Back-compat: also accepts legacy keys (chat_jid/sender_jid) and optional
|
|
114
|
+
// prose preface before the [bridge:...] metadata block.
|
|
115
|
+
export function parseBridgePrompt(raw: string): BridgePromptContext | null {
|
|
116
|
+
const start = raw.indexOf("[bridge:");
|
|
117
|
+
if (start < 0) return null;
|
|
118
|
+
const src = raw.slice(start);
|
|
119
|
+
|
|
120
|
+
const headers: Record<string, string> = {};
|
|
121
|
+
const lines = src.split("\n");
|
|
122
|
+
let i = 0;
|
|
123
|
+
for (; i < lines.length; i++) {
|
|
124
|
+
const line = lines[i];
|
|
125
|
+
if (line === "") { i++; break; }
|
|
126
|
+
const m = /^\[([a-z_]+):([\s\S]*)\]$/.exec(line);
|
|
127
|
+
if (!m) return null;
|
|
128
|
+
headers[m[1]] = m[2];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const chatId = headers.chat_id || headers.chat_jid;
|
|
132
|
+
const senderId = headers.sender_id || headers.sender_jid || chatId;
|
|
133
|
+
if (!headers.bridge || !chatId || !headers.chat_type) return null;
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
bridgeId: headers.bridge,
|
|
137
|
+
chatJid: chatId,
|
|
138
|
+
chatName: headers.chat_name || chatId,
|
|
139
|
+
isGroup: headers.chat_type === "group",
|
|
140
|
+
senderJid: senderId,
|
|
141
|
+
senderName: headers.sender_name || senderId || "Unknown",
|
|
142
|
+
body: lines.slice(i).join("\n").trimEnd(),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
100
146
|
function roleNote(role: MessageRole, isGroup: boolean): string {
|
|
101
147
|
switch (role) {
|
|
102
148
|
case "user":
|
|
@@ -75,12 +75,31 @@ describe("watcherHandler (ADR-0027)", () => {
|
|
|
75
75
|
expect(fired.agentId).toBe("a");
|
|
76
76
|
expect(fired.kind).toBe("watcher");
|
|
77
77
|
expect(fired.prompt).toContain('Watcher "change" detected a change');
|
|
78
|
-
expect(fired.prompt).toContain("
|
|
79
|
-
expect(fired.prompt).toContain("
|
|
78
|
+
expect(fired.prompt).toContain("--- Diff (previous -> current) ---");
|
|
79
|
+
expect(fired.prompt).toContain("- v1");
|
|
80
|
+
expect(fired.prompt).toContain("+ v2");
|
|
81
|
+
expect(fired.prompt).not.toContain("--- Previous result ---");
|
|
82
|
+
expect(fired.prompt).not.toContain("--- Current result ---");
|
|
80
83
|
const after = getWatcher(w.id)!;
|
|
81
84
|
expect(after.last_fired_at).not.toBeNull();
|
|
82
85
|
});
|
|
83
86
|
|
|
87
|
+
it("truncates oversized result payloads in the firing prompt", async () => {
|
|
88
|
+
const w = createWatcher({
|
|
89
|
+
agent_id: "a", label: "big", tool_name: "watcher_test_tool", interval_seconds: 60,
|
|
90
|
+
});
|
|
91
|
+
fakeResult = "A".repeat(5000);
|
|
92
|
+
await watcherHandler.getDueFirings(new Date(Date.parse(w.next_run_at) + 1));
|
|
93
|
+
fakeResult = "B".repeat(5000);
|
|
94
|
+
const w2 = getWatcher(w.id)!;
|
|
95
|
+
const firings = await watcherHandler.getDueFirings(new Date(Date.parse(w2.next_run_at) + 1));
|
|
96
|
+
expect(firings).toHaveLength(1);
|
|
97
|
+
const fired = firings[0];
|
|
98
|
+
if (fired.mode !== "prompt") throw new Error("expected prompt firing");
|
|
99
|
+
expect(fired.prompt).toContain("[diff truncated: showing");
|
|
100
|
+
expect(fired.prompt.length).toBeLessThan(8000);
|
|
101
|
+
});
|
|
102
|
+
|
|
84
103
|
it("does NOT fire when result is unchanged across polls", async () => {
|
|
85
104
|
const w = createWatcher({
|
|
86
105
|
agent_id: "a", label: "stable", tool_name: "watcher_test_tool", interval_seconds: 60,
|
|
@@ -123,8 +142,8 @@ describe("watcherHandler (ADR-0027)", () => {
|
|
|
123
142
|
expect(fired.prompt).toContain("Open a Jira ticket against the broken dashboard.");
|
|
124
143
|
expect(fired.prompt).not.toContain("Summarise what changed");
|
|
125
144
|
// Diff envelope is preserved.
|
|
126
|
-
expect(fired.prompt).toContain("v1");
|
|
127
|
-
expect(fired.prompt).toContain("v2");
|
|
145
|
+
expect(fired.prompt).toContain("- v1");
|
|
146
|
+
expect(fired.prompt).toContain("+ v2");
|
|
128
147
|
});
|
|
129
148
|
|
|
130
149
|
it("uses the default directive when reaction_prompt is null", async () => {
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// tools must be context-free).
|
|
7
7
|
// 3. Hash the stringified result and compare to last_fingerprint.
|
|
8
8
|
// 4. If the hash differs from the previous run, return a TriggerFiring
|
|
9
|
-
// whose prompt embeds
|
|
9
|
+
// whose prompt embeds a compact previous->current diff for the agent.
|
|
10
10
|
// If it matches (or this is the first run with no previous), record
|
|
11
11
|
// the fingerprint and skip — no LLM call, no firing.
|
|
12
12
|
// 5. Either way the watcher's next_run_at is advanced by
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
} from "@/lib/stores/watchers";
|
|
26
26
|
import { registeredTools } from "@/lib/tools/registry";
|
|
27
27
|
import { publish as publishNotification } from "@/lib/notifications/bus";
|
|
28
|
+
import { truncateBytes } from "@/lib/utils/text";
|
|
28
29
|
import type { TriggerFiring, TriggerHandler, TriggerOutcome } from "../types";
|
|
29
30
|
|
|
30
31
|
export const WATCHER_KIND = "watcher";
|
|
@@ -46,14 +47,64 @@ const DEFAULT_REACTION_DIRECTIVE =
|
|
|
46
47
|
`Summarise what changed and decide whether the user needs to know. ` +
|
|
47
48
|
`If nothing material changed, you may stay silent.`;
|
|
48
49
|
|
|
50
|
+
// Watcher tool outputs can be very large (full JSON payloads, long lists).
|
|
51
|
+
// Keep the diff context bounded so one firing cannot consume most of an
|
|
52
|
+
// agent's prompt budget.
|
|
53
|
+
const MAX_DIFF_CONTEXT_BYTES = 3500;
|
|
54
|
+
|
|
55
|
+
function normalizeForDiff(raw: string): string {
|
|
56
|
+
try {
|
|
57
|
+
return JSON.stringify(JSON.parse(raw), null, 2);
|
|
58
|
+
} catch {
|
|
59
|
+
return raw;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function buildDiffForPrompt(previous: string | null, current: string): string {
|
|
64
|
+
if (previous === null) return "+ (first observation baseline established; no diff available)";
|
|
65
|
+
|
|
66
|
+
const prev = normalizeForDiff(previous).split(/\r?\n/);
|
|
67
|
+
const curr = normalizeForDiff(current).split(/\r?\n/);
|
|
68
|
+
|
|
69
|
+
let start = 0;
|
|
70
|
+
while (start < prev.length && start < curr.length && prev[start] === curr[start]) {
|
|
71
|
+
start += 1;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let prevEnd = prev.length - 1;
|
|
75
|
+
let currEnd = curr.length - 1;
|
|
76
|
+
while (prevEnd >= start && currEnd >= start && prev[prevEnd] === curr[currEnd]) {
|
|
77
|
+
prevEnd -= 1;
|
|
78
|
+
currEnd -= 1;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const removed = prev.slice(start, prevEnd + 1);
|
|
82
|
+
const added = curr.slice(start, currEnd + 1);
|
|
83
|
+
if (removed.length === 0 && added.length === 0) {
|
|
84
|
+
return "(no textual diff after normalization)";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const hunkHeader = `@@ old:${start + 1}-${Math.max(start, prevEnd + 1)} new:${start + 1}-${Math.max(start, currEnd + 1)} @@`;
|
|
88
|
+
const raw = [
|
|
89
|
+
hunkHeader,
|
|
90
|
+
...removed.map((l) => `- ${l}`),
|
|
91
|
+
...added.map((l) => `+ ${l}`),
|
|
92
|
+
].join("\n");
|
|
93
|
+
|
|
94
|
+
const bytes = Buffer.byteLength(raw, "utf8");
|
|
95
|
+
const clipped = truncateBytes(raw, MAX_DIFF_CONTEXT_BYTES);
|
|
96
|
+
if (!clipped.truncated) return raw;
|
|
97
|
+
return `${clipped.text}\n… [diff truncated: showing ${MAX_DIFF_CONTEXT_BYTES} of ${bytes} bytes; full values retained in watcher state]`;
|
|
98
|
+
}
|
|
99
|
+
|
|
49
100
|
function buildFiringPrompt(watcher: WatcherRow, previous: string | null, current: string): string {
|
|
50
101
|
const argsPretty = (() => {
|
|
51
102
|
try { return JSON.stringify(JSON.parse(watcher.tool_args), null, 2); }
|
|
52
103
|
catch { return watcher.tool_args; }
|
|
53
104
|
})();
|
|
54
105
|
// ADR-0030: a non-null reaction_prompt swaps in for the default directive.
|
|
55
|
-
// The diff envelope (label/tool/args/
|
|
56
|
-
//
|
|
106
|
+
// The diff envelope (label/tool/args/diff) is unchanged so the agent
|
|
107
|
+
// always has the change context regardless of the user's instruction.
|
|
57
108
|
const directive = watcher.reaction_prompt?.trim() || DEFAULT_REACTION_DIRECTIVE;
|
|
58
109
|
return [
|
|
59
110
|
`Watcher "${watcher.label}" detected a change.`,
|
|
@@ -61,11 +112,8 @@ function buildFiringPrompt(watcher: WatcherRow, previous: string | null, current
|
|
|
61
112
|
`Tool: ${watcher.tool_name}`,
|
|
62
113
|
`Args: ${argsPretty}`,
|
|
63
114
|
``,
|
|
64
|
-
`---
|
|
65
|
-
previous
|
|
66
|
-
``,
|
|
67
|
-
`--- Current result ---`,
|
|
68
|
-
current,
|
|
115
|
+
`--- Diff (previous -> current) ---`,
|
|
116
|
+
buildDiffForPrompt(previous, current),
|
|
69
117
|
``,
|
|
70
118
|
directive,
|
|
71
119
|
].join("\n");
|
package/package.json
CHANGED