pi-harness-runtime 1.1.391 → 1.1.393
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/AGENTS.md +29 -8
- package/harness/telegram-auth.ts +32 -0
- package/harness/telegram-update.ts +125 -0
- package/index.ts +100 -27
- package/package.json +1 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/agent-policy/package.json +1 -1
- package/packages/architecture-generator/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/clipboard-plugin/package.json +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/codex-watcher/package.json +1 -1
- package/packages/config-capture/package.json +1 -1
- package/packages/connection-runtime/package.json +1 -1
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/contracts/dist/workspace-dotenv.d.ts +44 -0
- package/packages/contracts/dist/workspace-dotenv.js +111 -0
- package/packages/contracts/dist/workspace-env-format.d.ts +129 -6
- package/packages/contracts/dist/workspace-env-format.js +283 -11
- package/packages/contracts/dist/workspace-env.d.ts +10 -6
- package/packages/contracts/dist/workspace-env.js +36 -92
- package/packages/contracts/package.json +1 -1
- package/packages/contracts/src/workspace-dotenv.ts +114 -0
- package/packages/contracts/src/workspace-env-format.ts +318 -13
- package/packages/contracts/src/workspace-env.ts +62 -92
- package/packages/contracts/test/workspace-env.test.ts +398 -5
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/durable-review/package.json +1 -1
- package/packages/e2e-tester/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-sink/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/health-monitor/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/jev-judge/package.json +1 -1
- package/packages/knowledge-graph/package.json +1 -1
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/milestone-manager/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/dist/adapters/telegram-adapter.d.ts +95 -0
- package/packages/notification/dist/adapters/telegram-adapter.d.ts.map +1 -1
- package/packages/notification/dist/adapters/telegram-adapter.js +284 -18
- package/packages/notification/dist/adapters/telegram-adapter.js.map +1 -1
- package/packages/notification/dist/telegram-auth.d.ts +23 -0
- package/packages/notification/dist/telegram-auth.d.ts.map +1 -0
- package/packages/notification/dist/telegram-auth.js +32 -0
- package/packages/notification/dist/telegram-auth.js.map +1 -0
- package/packages/notification/dist/types.d.ts +5 -0
- package/packages/notification/dist/types.d.ts.map +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/notification/src/adapters/telegram-adapter.ts +283 -18
- package/packages/notification/src/telegram-auth.ts +34 -0
- package/packages/notification/src/types.ts +5 -0
- package/packages/observability/package.json +1 -1
- package/packages/okf-indexer/package.json +1 -1
- package/packages/paperclip-integration/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/permission-mode/package.json +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/project-analyzer/package.json +1 -1
- package/packages/project-bootstrap/package.json +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/qdrant-skills/package.json +1 -1
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/relay/dist/server.js +7 -2
- package/packages/relay/dist/types.d.ts +3 -1
- package/packages/relay/dist/ws-server.d.ts +2 -9
- package/packages/relay/dist/ws-server.js +5 -3
- package/packages/relay/package.json +1 -1
- package/packages/relay/src/server.ts +7 -2
- package/packages/relay/src/types.ts +6 -1
- package/packages/relay/src/ws-server.ts +9 -13
- package/packages/relay/test/relay-e2e.test.ts +36 -0
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-mcp-client/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/skills/package.json +1 -1
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/telegram/package.json +1 -1
- package/packages/tencentdb-memory/package.json +1 -1
- package/packages/tencentdb-sync/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/trajectory/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/voice-api/package.json +1 -1
- package/packages/webmcp-tester/package.json +1 -1
- package/packages/wiki/package.json +1 -1
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- package/packages/write-review/package.json +1 -1
- package/scripts/unified-webhook-server.ts +141 -14
- package/scripts/ws-server.ts +7 -1
package/AGENTS.md
CHANGED
|
@@ -494,27 +494,45 @@ bun run build && bun run start # Server deployment
|
|
|
494
494
|
### 14. Workspace Environment Auto-Detection
|
|
495
495
|
|
|
496
496
|
**At session start, the harness auto-detects:**
|
|
497
|
-
- `.env` file
|
|
497
|
+
- `.env` / `.env.*` file variables → Dev environment evidence
|
|
498
498
|
- `package.json` `dev` script → Hot reload capability
|
|
499
|
-
- `NODE_ENV`
|
|
499
|
+
- The environment variable, in this order: the `NODE_ENV` of the process env
|
|
500
|
+
(it decides, also when it names no known mode), then the dotenv files of the
|
|
501
|
+
workspace: `NODE_ENV`, an environment shaped key whose value names a known
|
|
502
|
+
mode (`*_ENV`, `*_ENVIRONMENT`, `*_MODE`, `*_STAGE`, e.g. `AMOS_ENV`), then
|
|
503
|
+
any other environment shaped key - and only then an environment shaped key
|
|
504
|
+
of the process env → development/production/test
|
|
500
505
|
- System RAM → Low RAM warning (<16GB total or <4GB available)
|
|
501
506
|
- Running processes → dev/build server status
|
|
502
507
|
|
|
508
|
+
The dotenv files are read from the WORKSPACE (not from the current working
|
|
509
|
+
directory), the template files (`.env.example`, `.env.sample`,
|
|
510
|
+
`.env.template`, ...) are ignored and a file bigger than 256 kB is not read.
|
|
511
|
+
A value that is not provably harmless is masked (`<set>`): only the
|
|
512
|
+
`NODE_ENV` of the process env and a value that IS the name of the mode are
|
|
513
|
+
shown. A key that only looks like a branch (`BRANCH=production`) never
|
|
514
|
+
decides the mode.
|
|
515
|
+
|
|
503
516
|
**This info is shown in UI and delivered to agent context:**
|
|
504
517
|
```
|
|
505
|
-
Workspace: ~/pi-harness-runtime | OS: Linux | mode: DEVELOPMENT | dev server: running | dev script: available | build script: available | RAM: 10/16
|
|
518
|
+
Workspace: ~/pi-harness-runtime | OS: Linux | mode: DEVELOPMENT (from AMOS_ENV in .env) | dev server: running | dev script: available | build script: available | RAM: 10/16
|
|
506
519
|
```
|
|
507
520
|
|
|
508
521
|
The mode words are exactly what the detector emits: `DEVELOPMENT`, `PRODUCTION`,
|
|
509
|
-
`TEST` or `UNKNOWN` (with its reason, e.g.
|
|
522
|
+
`TEST` or `UNKNOWN` (with its reason, e.g.
|
|
523
|
+
`UNKNOWN (no NODE_ENV or environment variable found)` or
|
|
524
|
+
`UNKNOWN (AMOS_ENV in .env names no known mode)`). A known mode also
|
|
525
|
+
names its source: `DEVELOPMENT (from AMOS_ENV in .env)`.
|
|
510
526
|
A dev/build script is a CAPABILITY, not a state: `dev script: available` means
|
|
511
527
|
the script exists, not that a server runs. The server state is separate and
|
|
512
528
|
never claims more than it knows: `running`, `not detected` (a check ran and
|
|
513
529
|
found nothing) or `not checked` (no check was possible, e.g. no `pgrep`).
|
|
514
530
|
|
|
515
531
|
The OS is part of the line because it decides which command prints the
|
|
516
|
-
working directory: `pwd` on Linux/macOS, `
|
|
517
|
-
|
|
532
|
+
working directory: `pwd` on Linux/macOS, `(pwd).Path` or `$pwd.Path` in
|
|
533
|
+
PowerShell (the plain `pwd` there prints a "Path: " header), `cd` or
|
|
534
|
+
`echo %cd%` in the Windows command prompt. The agent context names the
|
|
535
|
+
command to use.
|
|
518
536
|
|
|
519
537
|
**IMPORTANT Rules:**
|
|
520
538
|
|
|
@@ -528,8 +546,11 @@ The agent context names the command to use.
|
|
|
528
546
|
|
|
529
547
|
**Decision flow:**
|
|
530
548
|
```
|
|
531
|
-
Check process.env.NODE_ENV first
|
|
532
|
-
Check .env
|
|
549
|
+
Check process.env.NODE_ENV first (it decides, also when it names no known mode)
|
|
550
|
+
Check the .env and .env.* files of the workspace second
|
|
551
|
+
(NODE_ENV, then an environment shaped key whose value names a mode,
|
|
552
|
+
then any other environment shaped key)
|
|
553
|
+
Only then an environment shaped key of the process env (e.g. AMOS_ENV)
|
|
533
554
|
Presence of dev script = capability, not proof of mode
|
|
534
555
|
UNKNOWN requires investigation, not auto-build
|
|
535
556
|
Production detection ≠ deployment authorization
|
package/harness/telegram-auth.ts
CHANGED
|
@@ -90,6 +90,38 @@ export function getAllowedUserIds(): Set<number> {
|
|
|
90
90
|
return new Set();
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Describe who may write into Pi through Telegram.
|
|
95
|
+
*
|
|
96
|
+
* The allow-list falls back to the chat id file, and a GROUP chat id is
|
|
97
|
+
* negative - it identifies a chat, not a person. A sender rule that only
|
|
98
|
+
* knows a group id lets nobody in, silently. The description makes that
|
|
99
|
+
* visible.
|
|
100
|
+
*/
|
|
101
|
+
export function describeInboundAccess(
|
|
102
|
+
allowedUserIds: Iterable<number>,
|
|
103
|
+
chatId?: string,
|
|
104
|
+
): string {
|
|
105
|
+
const ids = [...allowedUserIds].filter((n) => Number.isFinite(n));
|
|
106
|
+
const users = ids.filter((n) => n > 0);
|
|
107
|
+
const groups = ids.filter((n) => n < 0);
|
|
108
|
+
if (users.length === 0) {
|
|
109
|
+
const known = groups.length > 0 ? ` (the allow-list holds the group chat id ${groups.join(", ")})` : "";
|
|
110
|
+
return `nobody may write in${known}`;
|
|
111
|
+
}
|
|
112
|
+
const chat = chatId ? ` in chat ${chatId}` : "";
|
|
113
|
+
return `user id(s) ${users.join(", ")} may write in${chat}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Decide whether a Telegram sender may write into Pi.
|
|
118
|
+
*
|
|
119
|
+
* The rule itself lives in the notification package
|
|
120
|
+
* (packages/notification/src/telegram-auth.ts) so that the adapter, this
|
|
121
|
+
* module and the remote webhook server all use ONE implementation.
|
|
122
|
+
*/
|
|
123
|
+
export { isAuthorizedTelegramSender } from "../packages/notification/dist/telegram-auth.js";
|
|
124
|
+
|
|
93
125
|
/**
|
|
94
126
|
* Check if a Telegram user ID is authorized
|
|
95
127
|
*/
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map a relay "telegram_message" payload onto a Telegram update.
|
|
3
|
+
*
|
|
4
|
+
* The mapping lives in a pure module so that it can be tested without the
|
|
5
|
+
* harness: the relay carries the text, the adapter decides whether the
|
|
6
|
+
* sender is allowed to write in.
|
|
7
|
+
*
|
|
8
|
+
* Rules:
|
|
9
|
+
* - the text is never put into an id field (ids are logged by the client)
|
|
10
|
+
* - the real bot flag has to survive the transport, otherwise the adapter
|
|
11
|
+
* could not refuse bot messages
|
|
12
|
+
* - a payload without text produces no update
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export interface TelegramMessageUpdate {
|
|
16
|
+
message: {
|
|
17
|
+
message_id: number;
|
|
18
|
+
from: {
|
|
19
|
+
id: number;
|
|
20
|
+
is_bot: boolean;
|
|
21
|
+
first_name: string;
|
|
22
|
+
username?: string;
|
|
23
|
+
};
|
|
24
|
+
chat: { id: number };
|
|
25
|
+
text: string;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface TelegramMessagePayload {
|
|
30
|
+
userId: number;
|
|
31
|
+
username?: string;
|
|
32
|
+
messageId?: number;
|
|
33
|
+
chatId?: number;
|
|
34
|
+
text?: string;
|
|
35
|
+
isBot?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface TelegramCallbackUpdate {
|
|
39
|
+
callback_query: {
|
|
40
|
+
id: string;
|
|
41
|
+
from: {
|
|
42
|
+
id: number;
|
|
43
|
+
is_bot: boolean;
|
|
44
|
+
first_name: string;
|
|
45
|
+
username?: string;
|
|
46
|
+
};
|
|
47
|
+
data: string;
|
|
48
|
+
message: {
|
|
49
|
+
chat: { id: number };
|
|
50
|
+
message_id: number;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Build the Telegram update for a relayed button callback.
|
|
57
|
+
*
|
|
58
|
+
* The real bot flag has to survive the transport: the adapter refuses
|
|
59
|
+
* messages and callbacks of a bot.
|
|
60
|
+
*/
|
|
61
|
+
export function buildTelegramCallbackUpdate(
|
|
62
|
+
payload: TelegramMessagePayload & {
|
|
63
|
+
action?: string;
|
|
64
|
+
targetId?: string;
|
|
65
|
+
callbackQueryId?: string;
|
|
66
|
+
id?: string;
|
|
67
|
+
},
|
|
68
|
+
): TelegramCallbackUpdate | null {
|
|
69
|
+
const userId = Number(payload.userId);
|
|
70
|
+
if (!Number.isFinite(userId)) return null;
|
|
71
|
+
const chatId = Number(payload.chatId);
|
|
72
|
+
const effectiveChatId = Number.isFinite(chatId) ? chatId : userId;
|
|
73
|
+
return {
|
|
74
|
+
callback_query: {
|
|
75
|
+
id: payload.callbackQueryId || String(payload.id || ""),
|
|
76
|
+
from: {
|
|
77
|
+
id: userId,
|
|
78
|
+
is_bot: payload.isBot === true,
|
|
79
|
+
first_name: payload.username || "User",
|
|
80
|
+
username: payload.username,
|
|
81
|
+
},
|
|
82
|
+
data: `${payload.action || ""}:${payload.targetId || ""}`,
|
|
83
|
+
message: {
|
|
84
|
+
chat: { id: effectiveChatId },
|
|
85
|
+
message_id: Number(payload.messageId) || 0,
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Build the Telegram update for a relayed text message.
|
|
93
|
+
*
|
|
94
|
+
* Returns null when the payload carries no text - there is nothing to
|
|
95
|
+
* forward and the adapter must not see an empty message.
|
|
96
|
+
*/
|
|
97
|
+
export function buildTelegramMessageUpdate(
|
|
98
|
+
payload: TelegramMessagePayload,
|
|
99
|
+
): TelegramMessageUpdate | null {
|
|
100
|
+
const text = payload.text ?? "";
|
|
101
|
+
if (!text) return null;
|
|
102
|
+
|
|
103
|
+
const userId = Number(payload.userId);
|
|
104
|
+
if (!Number.isFinite(userId)) return null;
|
|
105
|
+
|
|
106
|
+
// A direct message has the chat id of the user; the remote always sends
|
|
107
|
+
// the real chat id, so the fallback is only for a DM.
|
|
108
|
+
const chatId = Number(payload.chatId);
|
|
109
|
+
const effectiveChatId = Number.isFinite(chatId) ? chatId : userId;
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
message: {
|
|
113
|
+
message_id: Number(payload.messageId) || 0,
|
|
114
|
+
from: {
|
|
115
|
+
id: userId,
|
|
116
|
+
// The real flag: the adapter refuses bot messages.
|
|
117
|
+
is_bot: payload.isBot === true,
|
|
118
|
+
first_name: payload.username || "User",
|
|
119
|
+
username: payload.username,
|
|
120
|
+
},
|
|
121
|
+
chat: { id: effectiveChatId },
|
|
122
|
+
text,
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
package/index.ts
CHANGED
|
@@ -28,6 +28,8 @@ import {
|
|
|
28
28
|
type KeyFileAuditResult,
|
|
29
29
|
} from "./harness/key-file-audit.ts";
|
|
30
30
|
import { renderDetailLines } from "./harness/startup-lines.ts";
|
|
31
|
+
import { describeInboundAccess, getAllowedUserIds } from "./harness/telegram-auth.ts";
|
|
32
|
+
import { buildTelegramCallbackUpdate, buildTelegramMessageUpdate } from "./harness/telegram-update.ts";
|
|
31
33
|
import {
|
|
32
34
|
jevMissingKeyReport,
|
|
33
35
|
lineConfigurationReport,
|
|
@@ -1364,6 +1366,7 @@ async function initTelegram(pi: ExtensionAPI): Promise<void> {
|
|
|
1364
1366
|
return;
|
|
1365
1367
|
}
|
|
1366
1368
|
|
|
1369
|
+
const allowedUserIds = [...getAllowedUserIds()];
|
|
1367
1370
|
const channels: Array<{
|
|
1368
1371
|
id: string;
|
|
1369
1372
|
type: string;
|
|
@@ -1374,10 +1377,27 @@ async function initTelegram(pi: ExtensionAPI): Promise<void> {
|
|
|
1374
1377
|
id: "telegram",
|
|
1375
1378
|
type: "telegram",
|
|
1376
1379
|
enabled: true,
|
|
1377
|
-
config: { botToken, chatId },
|
|
1380
|
+
config: { botToken, chatId, allowedUserIds },
|
|
1378
1381
|
},
|
|
1379
1382
|
];
|
|
1380
1383
|
|
|
1384
|
+
// Who may write into Pi? A group chat id is not a user id: when the
|
|
1385
|
+
// allow-list holds only a group id, the inbound path is closed - and
|
|
1386
|
+
// that must be visible, not silent.
|
|
1387
|
+
const inboundAccess = describeInboundAccess(allowedUserIds, chatId);
|
|
1388
|
+
logStartup(`[pi-harness] Telegram inbound: ${inboundAccess}`);
|
|
1389
|
+
if (inboundAccess.startsWith("nobody")) {
|
|
1390
|
+
addHarnessMessage(
|
|
1391
|
+
"Telegram",
|
|
1392
|
+
"nobody may write in: the allow-list holds no user id",
|
|
1393
|
+
"warn",
|
|
1394
|
+
[
|
|
1395
|
+
"Write your Telegram user id into ~/.pi-harness-runtime/keys/telegram-allowed-users.txt (a chat id of a group is not a user id).",
|
|
1396
|
+
"Then reload Pi.",
|
|
1397
|
+
],
|
|
1398
|
+
);
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1381
1401
|
// Add LINE channel if credentials exist
|
|
1382
1402
|
const lineChannelConfigured = Boolean(hasLineCredentials && lineAccessToken && lineUserId);
|
|
1383
1403
|
if (lineChannelConfigured) {
|
|
@@ -1587,7 +1607,9 @@ async function initTelegram(pi: ExtensionAPI): Promise<void> {
|
|
|
1587
1607
|
return;
|
|
1588
1608
|
}
|
|
1589
1609
|
|
|
1590
|
-
|
|
1610
|
+
// The user's own words stay out of the log files: only the length
|
|
1611
|
+
// is recorded.
|
|
1612
|
+
logToFile("[TelegramInbound]", `Forwarding message to Pi (${text.length} chars)`);
|
|
1591
1613
|
// Forward the message to Pi as a user message
|
|
1592
1614
|
await pi.sendUserMessage(text, { deliverAs: "steer" });
|
|
1593
1615
|
});
|
|
@@ -1644,7 +1666,32 @@ async function initTelegram(pi: ExtensionAPI): Promise<void> {
|
|
|
1644
1666
|
]);
|
|
1645
1667
|
return;
|
|
1646
1668
|
}
|
|
1647
|
-
|
|
1669
|
+
// Polling and the remote webhook are mutually exclusive. Telegram
|
|
1670
|
+
// rejects getUpdates while a webhook is active, and the rejected poll
|
|
1671
|
+
// also terminates the getUpdates stream of every other poller of this
|
|
1672
|
+
// bot, which delayed the inbound text messages by seconds.
|
|
1673
|
+
if (telegram) {
|
|
1674
|
+
// A 409 (a webhook or another poller owns this bot) stops the
|
|
1675
|
+
// inbound path. The user must see it - a silent 409 looks like a
|
|
1676
|
+
// working inbound path.
|
|
1677
|
+
telegram.onPollStopped = (reason) => {
|
|
1678
|
+
addHarnessMessage("Telegram", `inbound polling stopped: ${reason}`, "warn", [
|
|
1679
|
+
"Telegram rejects getUpdates (409) while a webhook or another poller owns this bot.",
|
|
1680
|
+
"Stop the other poller of this bot (or remove the remote webhook), then reload Pi.",
|
|
1681
|
+
]);
|
|
1682
|
+
};
|
|
1683
|
+
if (!telegram.isWebhookConfigured()) {
|
|
1684
|
+
telegram.startPolling();
|
|
1685
|
+
// The poll may already have been rejected (a 409 fires the
|
|
1686
|
+
// callback above); make the reason visible either way.
|
|
1687
|
+
const stopReason = telegram.getPollStopReason();
|
|
1688
|
+
if (stopReason) {
|
|
1689
|
+
addHarnessMessage("Telegram", `inbound polling stopped: ${stopReason}`, "warn", [
|
|
1690
|
+
"Stop the other poller of this bot (or remove the remote webhook), then reload Pi.",
|
|
1691
|
+
]);
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1648
1695
|
if (!telegram?.isGreetingVerified()) {
|
|
1649
1696
|
addHarnessMessage("Telegram", "Hi sent; tap Say Hi to verify inbound (tap Toggle Telegram to disable)", "wait");
|
|
1650
1697
|
}
|
|
@@ -1931,33 +1978,57 @@ async function initRelayStream(): Promise<void> {
|
|
|
1931
1978
|
logToFile("[RelayStream]", "Synchronized - receiving live events");
|
|
1932
1979
|
},
|
|
1933
1980
|
onCallback: async (callback: CallbackPayload) => {
|
|
1934
|
-
logToFile(
|
|
1981
|
+
logToFile(
|
|
1982
|
+
"[RelayStream]",
|
|
1983
|
+
callback.type === "telegram_message"
|
|
1984
|
+
? `Message from ${callback.username || callback.userId} (${(callback.text || "").length} chars)`
|
|
1985
|
+
: `Callback: ${callback.action} ${callback.targetId}`,
|
|
1986
|
+
);
|
|
1935
1987
|
|
|
1936
1988
|
// Forward Telegram callbacks to the adapter
|
|
1937
1989
|
if (callback.type === "telegram_callback" && _relayTelegramAdapter) {
|
|
1938
|
-
//
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1990
|
+
// The mapping is a pure function (harness/telegram-update.ts) so
|
|
1991
|
+
// that it can be tested without the harness.
|
|
1992
|
+
const update = buildTelegramCallbackUpdate({
|
|
1993
|
+
id: callback.id,
|
|
1994
|
+
action: callback.action,
|
|
1995
|
+
targetId: callback.targetId,
|
|
1996
|
+
callbackQueryId: callback.callbackQueryId,
|
|
1997
|
+
userId: callback.userId,
|
|
1998
|
+
username: callback.username,
|
|
1999
|
+
messageId: callback.messageId,
|
|
2000
|
+
chatId: callback.chatId,
|
|
2001
|
+
isBot: callback.isBot,
|
|
2002
|
+
});
|
|
2003
|
+
if (update) {
|
|
2004
|
+
try {
|
|
2005
|
+
await _relayTelegramAdapter.handleWebhookUpdate(update);
|
|
2006
|
+
logToFile("[RelayStream]", "Callback processed by Telegram adapter");
|
|
2007
|
+
} catch (err) {
|
|
2008
|
+
logToFile("[RelayStream]", `Adapter error: ${oneLineError(err)}`);
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
1955
2012
|
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
2013
|
+
// Forward a Telegram TEXT message (the user typed it) to the adapter
|
|
2014
|
+
if (callback.type === "telegram_message" && _relayTelegramAdapter) {
|
|
2015
|
+
// The mapping is a pure function (harness/telegram-update.ts) so
|
|
2016
|
+
// that it can be tested without the harness.
|
|
2017
|
+
const update = buildTelegramMessageUpdate({
|
|
2018
|
+
userId: callback.userId,
|
|
2019
|
+
username: callback.username,
|
|
2020
|
+
messageId: callback.messageId,
|
|
2021
|
+
chatId: callback.chatId,
|
|
2022
|
+
text: callback.text,
|
|
2023
|
+
isBot: callback.isBot,
|
|
2024
|
+
});
|
|
2025
|
+
if (update) {
|
|
2026
|
+
try {
|
|
2027
|
+
await _relayTelegramAdapter.handleWebhookUpdate(update);
|
|
2028
|
+
logToFile("[RelayStream]", "Message processed by Telegram adapter");
|
|
2029
|
+
} catch (err) {
|
|
2030
|
+
logToFile("[RelayStream]", `Adapter error: ${oneLineError(err)}`);
|
|
2031
|
+
}
|
|
1961
2032
|
}
|
|
1962
2033
|
}
|
|
1963
2034
|
},
|
|
@@ -2708,8 +2779,10 @@ const HARNESS_LOG_FILE = "/tmp/pi-harness.log";
|
|
|
2708
2779
|
/** Log a harness message to file (not stderr - bypasses Pi's cursor tracking) */
|
|
2709
2780
|
function logToFile(...parts: string[]): void {
|
|
2710
2781
|
const msg = parts.join(" ");
|
|
2782
|
+
// A timestamp makes the log usable for latency measurements.
|
|
2783
|
+
const stamp = new Date().toISOString();
|
|
2711
2784
|
try {
|
|
2712
|
-
appendFileSync(HARNESS_LOG_FILE, msg
|
|
2785
|
+
appendFileSync(HARNESS_LOG_FILE, `[${stamp}] ${msg}\n`);
|
|
2713
2786
|
} catch {
|
|
2714
2787
|
// Ignore file write errors
|
|
2715
2788
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-harness/capability-registry",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.393",
|
|
4
4
|
"description": "Capability Registry for pi-harness-runtime — tracks model capabilities and enables capability-based routing",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading the dotenv files of a workspace.
|
|
3
|
+
*
|
|
4
|
+
* This module owns the file system part of the environment detection: it
|
|
5
|
+
* finds the ".env" files of a workspace, reads them and skips what must not
|
|
6
|
+
* be read (templates, huge files, unreadable files).
|
|
7
|
+
*
|
|
8
|
+
* It is deliberately free of any other dependency (no node:sqlite) so it
|
|
9
|
+
* can be unit tested everywhere: the detection module itself needs
|
|
10
|
+
* node:sqlite, which not every runtime provides.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* The biggest dotenv file that is read. A ".env" file is a few kilobytes;
|
|
14
|
+
* anything bigger is not a configuration file (and reading it on every
|
|
15
|
+
* session start would be a waste).
|
|
16
|
+
*/
|
|
17
|
+
export declare const DOTENV_MAX_BYTES: number;
|
|
18
|
+
/**
|
|
19
|
+
* True for a template file that only documents the variables
|
|
20
|
+
* (".env.example", ".env.sample", ".env.template", ...).
|
|
21
|
+
*
|
|
22
|
+
* A template is not the configuration of the workspace: it must never
|
|
23
|
+
* decide the mode. The marker is looked for in EVERY segment of the
|
|
24
|
+
* suffix, so ".env.example.local" is a template too.
|
|
25
|
+
*/
|
|
26
|
+
export declare function isTemplateDotenvName(name: string): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* The dotenv files of a workspace as FULL paths, in the order they are read.
|
|
29
|
+
*
|
|
30
|
+
* ".env" comes first, then the well known variants, then every other
|
|
31
|
+
* ".env.*" file. Template files are skipped.
|
|
32
|
+
*/
|
|
33
|
+
export declare function listDotenvFiles(workspacePath: string): string[];
|
|
34
|
+
/**
|
|
35
|
+
* Read the dotenv files of a workspace.
|
|
36
|
+
*
|
|
37
|
+
* The files are read through the full paths of listDotenvFiles, so the
|
|
38
|
+
* workspace - not the current working directory - decides which files are
|
|
39
|
+
* read.
|
|
40
|
+
*/
|
|
41
|
+
export declare function readDotenvFiles(workspacePath: string): {
|
|
42
|
+
name: string;
|
|
43
|
+
content: string;
|
|
44
|
+
}[];
|