arisa 4.2.2 → 4.2.6
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 +13 -0
- package/package.json +1 -1
- package/src/core/agent/auth-flow.js +3 -3
- package/src/core/agent/pi-auth-login.js +2 -1
- package/src/core/agent/pi-runtime.js +2 -1
- package/src/runtime/bootstrap.js +3 -2
- package/src/runtime/paths.js +4 -1
- package/src/transport/telegram/bot.js +7 -4
- package/src/transport/telegram/device-code-message.js +48 -0
- package/src/transport/telegram/media.js +22 -0
- package/src/transport/telegram/text-format.js +1 -1
- package/test/device-code-message.test.js +42 -0
- package/test/paths.test.js +28 -0
- package/test/telegram-text-artifact.test.js +61 -0
package/README.md
CHANGED
|
@@ -86,6 +86,7 @@ All runtime state lives under `~/.arisa/`, split between global state and per-ch
|
|
|
86
86
|
|
|
87
87
|
Global:
|
|
88
88
|
- runtime config is stored in `~/.arisa/state/config.json`
|
|
89
|
+
- Pi OAuth credentials are stored in `~/.arisa/state/pi-auth.json`
|
|
89
90
|
- the scheduled-task queue is stored in `~/.arisa/state/tasks.json`
|
|
90
91
|
- installed tools live under `~/.arisa/tools/<tool>/`, each with a default `config.js` template
|
|
91
92
|
- global tool runtime state (daemons, caches, temp) lives under `~/.arisa/state/tools/<tool>/`
|
|
@@ -134,6 +135,17 @@ Notes:
|
|
|
134
135
|
|
|
135
136
|
- it only affects the current Arisa process and does not update `~/.arisa/state/config.json`
|
|
136
137
|
|
|
138
|
+
## Multiple instances
|
|
139
|
+
|
|
140
|
+
Set `ARISA_HOME` to run separate Arisa instances on the same machine:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
ARISA_HOME=~/.arisa-work arisa
|
|
144
|
+
ARISA_HOME=~/.arisa-personal arisa start
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Each home has its own Telegram config, Pi login, installed tools, daemons, chat sessions, artifacts, IPC socket, PID file, and logs. Pi OAuth credentials are stored in `<home>/state/pi-auth.json`, so existing installs authenticate once again after upgrading.
|
|
148
|
+
|
|
137
149
|
## Bootstrap flow
|
|
138
150
|
|
|
139
151
|
On first run, Arisa walks you through three steps:
|
|
@@ -156,6 +168,7 @@ src/
|
|
|
156
168
|
~/.arisa/
|
|
157
169
|
state/ global config, task queue, IPC socket
|
|
158
170
|
config.json
|
|
171
|
+
pi-auth.json
|
|
159
172
|
tasks.json
|
|
160
173
|
tools/<tool>/ global tool state (daemons, caches, tmp)
|
|
161
174
|
tools/<tool>/ installed tools (catalog, user-chosen, or agent-created)
|
package/package.json
CHANGED
|
@@ -78,11 +78,11 @@ export function buildPiAuthTelegramMessage({ config, issue = null, verified = fa
|
|
|
78
78
|
if (verified) {
|
|
79
79
|
lines.push("No action needed.");
|
|
80
80
|
} else if (!issue) {
|
|
81
|
-
lines.push("Run
|
|
81
|
+
lines.push("Run /auth to validate these credentials against the provider.");
|
|
82
82
|
} else if (status.hasApiKey) {
|
|
83
83
|
lines.push("A Pi API key is configured, but the provider rejected the current request. Update the key and restart Arisa.");
|
|
84
84
|
} else if (status.supportsOAuth) {
|
|
85
|
-
lines.push("Run
|
|
85
|
+
lines.push("Run /auth here in Telegram to renew the Pi login.");
|
|
86
86
|
} else {
|
|
87
87
|
lines.push("This provider needs a Pi API key. Re-run `arisa --bootstrap`, provide a key, and restart Arisa.");
|
|
88
88
|
}
|
|
@@ -103,7 +103,7 @@ export function buildPiAuthRecoveryBlockedMessage({ config, issue = null, renewa
|
|
|
103
103
|
|
|
104
104
|
lines.push(renewalActive
|
|
105
105
|
? "A Pi login is already in progress. Paste the redirect URL or code here when the provider gives it to you."
|
|
106
|
-
: "Send
|
|
106
|
+
: "Send /auth to start Pi login from Telegram.");
|
|
107
107
|
|
|
108
108
|
return lines.join("\n");
|
|
109
109
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { AuthStorage } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { piAuthFile } from "../../runtime/paths.js";
|
|
2
3
|
|
|
3
4
|
export function createPiOAuthLogin({ provider, onAuth, onDeviceCode, onPrompt, onProgress, onSelect } = {}) {
|
|
4
|
-
const authStorage = AuthStorage.create();
|
|
5
|
+
const authStorage = AuthStorage.create(piAuthFile);
|
|
5
6
|
const oauthProvider = authStorage.getOAuthProviders().find((item) => item.id === provider);
|
|
6
7
|
if (!oauthProvider) {
|
|
7
8
|
throw new Error(`No internal OAuth login flow is available for ${provider}.`);
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { AuthStorage, ModelRegistry } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { piAuthFile } from "../../runtime/paths.js";
|
|
2
3
|
|
|
3
4
|
function compareText(a, b) {
|
|
4
5
|
return a.localeCompare(b, undefined, { sensitivity: "base", numeric: true });
|
|
5
6
|
}
|
|
6
7
|
|
|
7
8
|
export function createPiRuntime({ provider, apiKey } = {}) {
|
|
8
|
-
const authStorage = AuthStorage.create();
|
|
9
|
+
const authStorage = AuthStorage.create(piAuthFile);
|
|
9
10
|
if (provider && apiKey) {
|
|
10
11
|
authStorage.setRuntimeApiKey(provider, apiKey);
|
|
11
12
|
}
|
package/src/runtime/bootstrap.js
CHANGED
|
@@ -6,6 +6,7 @@ import { spawn } from "node:child_process";
|
|
|
6
6
|
import { Bot } from "grammy";
|
|
7
7
|
import { createPiOAuthLogin } from "../core/agent/pi-auth-login.js";
|
|
8
8
|
import { createPiRuntime, hasProviderAuth, listPiProviders, listProviderModels, supportsProviderOAuth } from "../core/agent/pi-runtime.js";
|
|
9
|
+
import { buildDeviceCodeTelegramMessage } from "../transport/telegram/device-code-message.js";
|
|
9
10
|
import { configFile, ensureArisaHome } from "./paths.js";
|
|
10
11
|
|
|
11
12
|
const ARISA_BANNER = [
|
|
@@ -424,8 +425,8 @@ async function runTelegramBootstrap({ telegramApiKey, setupToken, botInfo }) {
|
|
|
424
425
|
].join("\n"));
|
|
425
426
|
},
|
|
426
427
|
onDeviceCode: async ({ userCode, verificationUri, expiresInSeconds }) => {
|
|
427
|
-
const
|
|
428
|
-
await sendSetupMessage(
|
|
428
|
+
const { text, ...options } = buildDeviceCodeTelegramMessage({ userCode, verificationUri, expiresInSeconds });
|
|
429
|
+
await sendSetupMessage(text, options);
|
|
429
430
|
},
|
|
430
431
|
onPrompt: async ({ message, controller }) => {
|
|
431
432
|
await sendSetupMessage(`${message}\nReply here with the value.`);
|
package/src/runtime/paths.js
CHANGED
|
@@ -5,9 +5,12 @@ import path from "node:path";
|
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
|
|
7
7
|
export const arisaPackageDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
8
|
-
export const arisaHomeDir =
|
|
8
|
+
export const arisaHomeDir = process.env.ARISA_HOME
|
|
9
|
+
? path.resolve(process.env.ARISA_HOME)
|
|
10
|
+
: path.join(os.homedir(), ".arisa");
|
|
9
11
|
export const stateDir = path.join(arisaHomeDir, "state");
|
|
10
12
|
export const configFile = path.join(stateDir, "config.json");
|
|
13
|
+
export const piAuthFile = path.join(stateDir, "pi-auth.json");
|
|
11
14
|
export const servicePidFile = path.join(stateDir, "arisa.pid");
|
|
12
15
|
export const serviceLogFile = path.join(stateDir, "arisa.log");
|
|
13
16
|
export function createIpcSocketPath({ homeDir = arisaHomeDir, platform = process.platform } = {}) {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Bot, InputFile } from "grammy";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { authorizeChat } from "./auth.js";
|
|
4
|
-
import { captureIncomingArtifact } from "./media.js";
|
|
4
|
+
import { captureIncomingArtifact, formatLocationText } from "./media.js";
|
|
5
|
+
import { buildDeviceCodeTelegramMessage } from "./device-code-message.js";
|
|
5
6
|
import { renderTelegramHtml } from "./text-format.js";
|
|
6
7
|
import { buildPiAuthRecoveryBlockedMessage, buildPiAuthTelegramMessage, getErrorMessage, getPiAuthIssue, getPiAuthStatus } from "../../core/agent/auth-flow.js";
|
|
7
8
|
import { createPiOAuthLogin } from "../../core/agent/pi-auth-login.js";
|
|
@@ -29,6 +30,7 @@ function quotedMessageSummary(message) {
|
|
|
29
30
|
if (message.document) parts.push(`quotedKind: document`);
|
|
30
31
|
if (message.video) parts.push(`quotedKind: video`);
|
|
31
32
|
if (message.sticker) parts.push(`quotedKind: sticker`);
|
|
33
|
+
if (message.location) parts.push(`quotedKind: location`, `quotedLocation: ${formatLocationText(message)}`);
|
|
32
34
|
|
|
33
35
|
if (!message.text && !message.caption) {
|
|
34
36
|
parts.push(`Important: this message replies to a Telegram message with no textual body available in the update. Use the quoted kind and metadata as context.`);
|
|
@@ -45,7 +47,7 @@ function getTelegramCommand(ctx) {
|
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
function getIncomingMessageText(message) {
|
|
48
|
-
return message?.text || message?.caption || "";
|
|
50
|
+
return message?.text || message?.caption || formatLocationText(message) || "";
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
function baseMimeType(mimeType = "") {
|
|
@@ -364,8 +366,9 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
364
366
|
].join("\n"));
|
|
365
367
|
},
|
|
366
368
|
onDeviceCode: async ({ userCode, verificationUri, expiresInSeconds }) => {
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
+
const payload = buildDeviceCodeTelegramMessage({ userCode, verificationUri, expiresInSeconds });
|
|
370
|
+
const { text, ...options } = payload;
|
|
371
|
+
await bot.api.sendMessage(chatId, text, options);
|
|
369
372
|
},
|
|
370
373
|
onPrompt: async ({ message, controller }) => {
|
|
371
374
|
await bot.api.sendMessage(chatId, `${message}\nReply here with the value.`);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { escapeHtml } from "./text-format.js";
|
|
2
|
+
|
|
3
|
+
const maxCopyTextLength = 256;
|
|
4
|
+
const maxButtonLabelLength = 64;
|
|
5
|
+
|
|
6
|
+
function truncateButtonLabel(text = "") {
|
|
7
|
+
const source = String(text || "").trim();
|
|
8
|
+
if (source.length <= maxButtonLabelLength) return source;
|
|
9
|
+
return `${source.slice(0, maxButtonLabelLength - 1)}…`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function buildDeviceCodeTelegramMessage({ userCode, verificationUri, expiresInSeconds } = {}) {
|
|
13
|
+
const code = String(userCode || "").trim();
|
|
14
|
+
const uri = String(verificationUri || "").trim();
|
|
15
|
+
const expiry = expiresInSeconds
|
|
16
|
+
? `\nExpires in ${Math.round(expiresInSeconds / 60)} minute(s).`
|
|
17
|
+
: "";
|
|
18
|
+
|
|
19
|
+
const lines = [
|
|
20
|
+
"Open this URL:",
|
|
21
|
+
uri ? `<a href="${escapeHtml(uri)}">${escapeHtml(uri)}</a>` : "",
|
|
22
|
+
"Then enter code:",
|
|
23
|
+
code ? `<code>${escapeHtml(code)}</code>` : "",
|
|
24
|
+
escapeHtml(expiry.trim())
|
|
25
|
+
].filter(Boolean);
|
|
26
|
+
|
|
27
|
+
const payload = {
|
|
28
|
+
text: lines.join("\n"),
|
|
29
|
+
parse_mode: "HTML",
|
|
30
|
+
link_preview_options: { is_disabled: true }
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const buttons = [];
|
|
34
|
+
if (code && code.length <= maxCopyTextLength) {
|
|
35
|
+
buttons.push({
|
|
36
|
+
text: truncateButtonLabel(`Copy code: ${code}`),
|
|
37
|
+
copy_text: { text: code }
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
if (uri) {
|
|
41
|
+
buttons.push({ text: "Open login page", url: uri });
|
|
42
|
+
}
|
|
43
|
+
if (buttons.length) {
|
|
44
|
+
payload.reply_markup = { inline_keyboard: [buttons] };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return payload;
|
|
48
|
+
}
|
|
@@ -32,6 +32,20 @@ function incomingCaptionMetadata(ctx) {
|
|
|
32
32
|
return ctx.message?.caption ? { caption: ctx.message.caption } : {};
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
export function formatLocationText(message) {
|
|
36
|
+
const location = message?.venue?.location || message?.location;
|
|
37
|
+
if (!location) return "";
|
|
38
|
+
|
|
39
|
+
const venue = message.venue;
|
|
40
|
+
const lines = [];
|
|
41
|
+
if (venue?.title) lines.push(`Venue: ${venue.title}`);
|
|
42
|
+
if (venue?.address) lines.push(`Address: ${venue.address}`);
|
|
43
|
+
lines.push(`Latitude: ${location.latitude}`);
|
|
44
|
+
lines.push(`Longitude: ${location.longitude}`);
|
|
45
|
+
lines.push(`Maps: https://maps.google.com/?q=${location.latitude},${location.longitude}`);
|
|
46
|
+
return lines.join("\n");
|
|
47
|
+
}
|
|
48
|
+
|
|
35
49
|
export async function captureIncomingArtifact(ctx, artifactStore) {
|
|
36
50
|
const chatId = ctx.chat.id;
|
|
37
51
|
const store = artifactStore.forChat(chatId);
|
|
@@ -124,6 +138,14 @@ export async function captureIncomingArtifact(ctx, artifactStore) {
|
|
|
124
138
|
});
|
|
125
139
|
}
|
|
126
140
|
|
|
141
|
+
if (ctx.message?.location) {
|
|
142
|
+
return store.createText({
|
|
143
|
+
text: formatLocationText(ctx.message),
|
|
144
|
+
source: baseSource,
|
|
145
|
+
metadata: { visibility: "internal", representation: "inline-message" }
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
127
149
|
if (ctx.message?.text) {
|
|
128
150
|
return store.createText({
|
|
129
151
|
text: ctx.message.text,
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { buildDeviceCodeTelegramMessage } from "../src/transport/telegram/device-code-message.js";
|
|
4
|
+
|
|
5
|
+
test("builds a copyable device-code Telegram message with HTML and inline buttons", () => {
|
|
6
|
+
const payload = buildDeviceCodeTelegramMessage({
|
|
7
|
+
userCode: "TR2S-K4GBZ",
|
|
8
|
+
verificationUri: "https://auth.openai.com/codex/device",
|
|
9
|
+
expiresInSeconds: 900
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
assert.equal(payload.parse_mode, "HTML");
|
|
13
|
+
assert.match(payload.text, /https:\/\/auth\.openai\.com\/codex\/device/);
|
|
14
|
+
assert.match(payload.text, /<code>TR2S-K4GBZ<\/code>/);
|
|
15
|
+
assert.match(payload.text, /Expires in 15 minute\(s\)\./);
|
|
16
|
+
assert.deepEqual(payload.reply_markup.inline_keyboard, [[
|
|
17
|
+
{ text: "Copy code: TR2S-K4GBZ", copy_text: { text: "TR2S-K4GBZ" } },
|
|
18
|
+
{ text: "Open login page", url: "https://auth.openai.com/codex/device" }
|
|
19
|
+
]]);
|
|
20
|
+
assert.deepEqual(payload.link_preview_options, { is_disabled: true });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("escapes HTML-sensitive device-code values", () => {
|
|
24
|
+
const payload = buildDeviceCodeTelegramMessage({
|
|
25
|
+
userCode: "A&B<1>",
|
|
26
|
+
verificationUri: "https://example.com/?q=\"x\""
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
assert.match(payload.text, /<code>A&B<1><\/code>/);
|
|
30
|
+
assert.match(payload.text, /href="https:\/\/example\.com\/\?q="x""/);
|
|
31
|
+
assert.equal(payload.reply_markup.inline_keyboard[0][0].copy_text.text, "A&B<1>");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("omits copy button when code exceeds Telegram copy_text limit", () => {
|
|
35
|
+
const payload = buildDeviceCodeTelegramMessage({
|
|
36
|
+
userCode: "x".repeat(257),
|
|
37
|
+
verificationUri: "https://example.com/device"
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
assert.equal(payload.reply_markup.inline_keyboard[0].length, 1);
|
|
41
|
+
assert.equal(payload.reply_markup.inline_keyboard[0][0].text, "Open login page");
|
|
42
|
+
});
|
package/test/paths.test.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
4
|
+
import os from "node:os";
|
|
2
5
|
import path from "node:path";
|
|
3
6
|
import test from "node:test";
|
|
7
|
+
import { promisify } from "node:util";
|
|
4
8
|
import {
|
|
5
9
|
chatsDir,
|
|
6
10
|
createIpcSocketPath,
|
|
@@ -11,6 +15,8 @@ import {
|
|
|
11
15
|
stateDir
|
|
12
16
|
} from "../src/runtime/paths.js";
|
|
13
17
|
|
|
18
|
+
const execFileAsync = promisify(execFile);
|
|
19
|
+
|
|
14
20
|
test("keeps chat artifact paths scoped below the chat directory", () => {
|
|
15
21
|
const artifactsDir = getChatArtifactsDir("chat-1");
|
|
16
22
|
|
|
@@ -52,3 +58,25 @@ test("creates POSIX IPC socket paths under the state directory", () => {
|
|
|
52
58
|
|
|
53
59
|
assert.equal(socketPath, path.join("/tmp/arisa-home", "state", "arisa.sock"));
|
|
54
60
|
});
|
|
61
|
+
|
|
62
|
+
test("uses ARISA_HOME for instance-scoped paths", async (t) => {
|
|
63
|
+
const customHome = await mkdtemp(path.join(os.tmpdir(), "arisa-home-"));
|
|
64
|
+
t.after(() => rm(customHome, { recursive: true, force: true }));
|
|
65
|
+
|
|
66
|
+
const script = `
|
|
67
|
+
const paths = await import(${JSON.stringify(new URL("../src/runtime/paths.js", import.meta.url).href)});
|
|
68
|
+
process.stdout.write(JSON.stringify({
|
|
69
|
+
arisaHomeDir: paths.arisaHomeDir,
|
|
70
|
+
configFile: paths.configFile,
|
|
71
|
+
piAuthFile: paths.piAuthFile
|
|
72
|
+
}));
|
|
73
|
+
`;
|
|
74
|
+
const { stdout } = await execFileAsync(process.execPath, ["--input-type=module", "--eval", script], {
|
|
75
|
+
env: { ...process.env, ARISA_HOME: customHome }
|
|
76
|
+
});
|
|
77
|
+
const paths = JSON.parse(stdout);
|
|
78
|
+
|
|
79
|
+
assert.equal(paths.arisaHomeDir, path.resolve(customHome));
|
|
80
|
+
assert.equal(paths.configFile, path.join(customHome, "state", "config.json"));
|
|
81
|
+
assert.equal(paths.piAuthFile, path.join(customHome, "state", "pi-auth.json"));
|
|
82
|
+
});
|
|
@@ -79,3 +79,64 @@ test("marks incoming Telegram text artifacts as internal inline messages", async
|
|
|
79
79
|
representation: "inline-message"
|
|
80
80
|
});
|
|
81
81
|
});
|
|
82
|
+
|
|
83
|
+
function createLocationContext({ location, venue } = {}) {
|
|
84
|
+
return {
|
|
85
|
+
chat: { id: 123 },
|
|
86
|
+
from: { id: 456, username: "martin" },
|
|
87
|
+
msg: { message_id: 789 },
|
|
88
|
+
message: {
|
|
89
|
+
message_id: 789,
|
|
90
|
+
location: location || { latitude: 40.4168, longitude: -3.7038 },
|
|
91
|
+
...(venue ? { venue } : {})
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
test("turns a shared Telegram location into a processable text artifact", async () => {
|
|
97
|
+
const artifactStore = {
|
|
98
|
+
forChat: () => ({
|
|
99
|
+
createText: async (request) => ({ id: "artifact-1", kind: "text", mimeType: "text/plain", ...request })
|
|
100
|
+
})
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const artifact = await captureIncomingArtifact(createLocationContext(), artifactStore);
|
|
104
|
+
|
|
105
|
+
assert.match(artifact.text, /Latitude: 40\.4168/);
|
|
106
|
+
assert.match(artifact.text, /Longitude: -3\.7038/);
|
|
107
|
+
assert.match(artifact.text, /Maps: https:\/\/maps\.google\.com\/\?q=40\.4168,-3\.7038/);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("includes venue title and address when a location is shared as a venue", async () => {
|
|
111
|
+
const artifactStore = {
|
|
112
|
+
forChat: () => ({
|
|
113
|
+
createText: async (request) => ({ id: "artifact-1", kind: "text", mimeType: "text/plain", ...request })
|
|
114
|
+
})
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const ctx = createLocationContext({
|
|
118
|
+
venue: { title: "Cafe Tortoni", address: "Av. de Mayo 825", location: { latitude: -34.6083, longitude: -58.3712 } }
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const artifact = await captureIncomingArtifact(ctx, artifactStore);
|
|
122
|
+
|
|
123
|
+
assert.match(artifact.text, /Venue: Cafe Tortoni/);
|
|
124
|
+
assert.match(artifact.text, /Address: Av\. de Mayo 825/);
|
|
125
|
+
assert.match(artifact.text, /Latitude: -34\.6083/);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("surfaces shared location coordinates inline in the agent prompt", () => {
|
|
129
|
+
const ctx = createLocationContext();
|
|
130
|
+
const prompt = buildPrompt({
|
|
131
|
+
ctx,
|
|
132
|
+
artifact: {
|
|
133
|
+
id: "artifact-1",
|
|
134
|
+
kind: "text",
|
|
135
|
+
mimeType: "text/plain",
|
|
136
|
+
text: "Latitude: 40.4168\nLongitude: -3.7038\nMaps: https://maps.google.com/?q=40.4168,-3.7038"
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
assert.match(prompt, /text: Latitude: 40\.4168/);
|
|
141
|
+
assert.doesNotMatch(prompt, /artifactId: artifact-1/);
|
|
142
|
+
});
|