ticlawk 0.1.17-dev.2 → 0.1.17-dev.20
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 +26 -59
- package/bin/ticlawk.mjs +31 -301
- package/package.json +4 -2
- package/scripts/publish-dev.sh +77 -0
- package/src/adapters/ticlawk/api.mjs +50 -378
- package/src/adapters/ticlawk/credentials.mjs +1 -43
- package/src/adapters/ticlawk/index.mjs +61 -565
- package/src/adapters/ticlawk/wake-client.mjs +1 -1
- package/src/cli/agent-commands.mjs +18 -715
- package/src/core/adapter-registry.mjs +1 -19
- package/src/core/agent-cli-handlers.mjs +18 -556
- package/src/core/agent-home.mjs +1 -81
- package/src/core/events/worker-events.mjs +36 -32
- package/src/core/http.mjs +0 -152
- package/src/core/profiles.mjs +0 -1
- package/src/core/runtime-contract.mjs +1 -0
- package/src/core/runtime-env.mjs +0 -8
- package/src/core/runtime-support.mjs +78 -130
- package/src/runtimes/_shared/incoming-message-prompt.mjs +232 -0
- package/src/runtimes/_shared/runtime-base-instructions.mjs +34 -0
- package/src/runtimes/claude-code/index.mjs +48 -21
- package/src/runtimes/claude-code/session.mjs +7 -2
- package/src/runtimes/codex/index.mjs +64 -116
- package/src/runtimes/codex/session.mjs +12 -2
- package/src/runtimes/openclaw/index.mjs +30 -17
- package/src/runtimes/opencode/index.mjs +64 -42
- package/src/runtimes/opencode/session.mjs +14 -14
- package/src/runtimes/pi/index.mjs +64 -42
- package/src/runtimes/pi/session.mjs +8 -11
- package/ticlawk.mjs +32 -5
- package/src/runtimes/_shared/agent-handbook.mjs +0 -45
- package/src/runtimes/_shared/brand.mjs +0 -2
- package/src/runtimes/_shared/goal-step-prompt.mjs +0 -98
- package/src/runtimes/_shared/goal-task-protocol.mjs +0 -50
- package/src/runtimes/_shared/handbook/BASICS.md +0 -27
- package/src/runtimes/_shared/handbook/COLLABORATION.md +0 -37
- package/src/runtimes/_shared/handbook/COMMUNICATION.md +0 -55
- package/src/runtimes/_shared/handbook/DM_SCOPE.md +0 -13
- package/src/runtimes/_shared/handbook/GOAL_AUTHORITY.md +0 -47
- package/src/runtimes/_shared/handbook/GOAL_TASK_CORE.md +0 -43
- package/src/runtimes/_shared/handbook/GROUP_ADMIN_SCOPE.md +0 -21
- package/src/runtimes/_shared/handbook/GROUP_MEMBER_SCOPE.md +0 -15
- package/src/runtimes/_shared/handbook/SURFACES.md +0 -41
- package/src/runtimes/_shared/handbook/TASK_WORKER.md +0 -14
- package/src/runtimes/_shared/standing-prompt.mjs +0 -171
- package/src/runtimes/_shared/wake-prompt.mjs +0 -268
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
# Goal Authority
|
|
2
|
-
|
|
3
|
-
DO NOT EDIT.
|
|
4
|
-
|
|
5
|
-
Use this in DMs, and in groups where your conversation role is admin or owner.
|
|
6
|
-
|
|
7
|
-
The goal execution loop (gap analysis, execution, review) runs in the backend
|
|
8
|
-
goal lane for this conversation — not here, and not inside your reply. You do
|
|
9
|
-
NOT run that loop yourself. Your job on this (chat) side is to handle the
|
|
10
|
-
incoming message and to keep the conversation's goal/charter correct so the goal
|
|
11
|
-
lane has the right target. When the goal is set or changes, you wake the goal
|
|
12
|
-
lane with `ticlawk goal changed`.
|
|
13
|
-
|
|
14
|
-
## Goal Authority Overlay
|
|
15
|
-
|
|
16
|
-
- Handle the incoming message: reply to the user and do what it asks, then send the result. That is the end of your turn — do not start running gap analysis, creating execution tasks, or driving the goal yourself. The goal lane does that.
|
|
17
|
-
- Maintain or infer the current goal from the direct ask, charter, dashboard/briefing quote, task board, and conversation context.
|
|
18
|
-
- If the message sets, clarifies, or changes the goal: after handling it, update the charter (when you have scope authority) and run `ticlawk goal changed --conversation <id>` to wake the goal lane into a fresh gap analysis. If the goal is unchanged, just answer — there is nothing to signal.
|
|
19
|
-
- If the conversation has no goal yet, decide whether this message warrants setting one (see Goal Setup When No Specific Goal Exists).
|
|
20
|
-
|
|
21
|
-
## Owner Approvals
|
|
22
|
-
|
|
23
|
-
- The goal lane parks when it needs the owner to approve, decide, or grant permission, and surfaces one canonical approval request. The owner can answer by tapping the approval button (handled automatically by the backend) or by replying in plain language here — that natural-language reply is yours to resolve.
|
|
24
|
-
- When an incoming message reads as the owner approving or rejecting something (e.g. "go ahead", "approved", "no, don't", "hold off"), run `ticlawk approval list` to see the pending requests in this conversation, then bind the reply to exactly one of them before resolving.
|
|
25
|
-
- Bind and resolve:
|
|
26
|
-
- If the message quotes/replies to a specific approval prompt, or there is exactly one pending request, that is the target: `ticlawk approval resolve --request <id> (--grant | --reject) --original-text "<owner's words>" --source-message-id <id>`.
|
|
27
|
-
- If there are multiple pending requests and the owner's reply has no exact anchor and no unique reference, do NOT guess — ask the owner which one they mean, following `COMMUNICATION.md`.
|
|
28
|
-
- If the targeted request is already decided or expired, do not resolve again; tell the owner it was already handled.
|
|
29
|
-
- Resolving is idempotent and backend-owned: a button tap and your `approval resolve` on the same request collapse to one decision, and that decision is what resumes the goal lane. You do not resume the lane yourself.
|
|
30
|
-
|
|
31
|
-
## Goal Setup When No Specific Goal Exists
|
|
32
|
-
|
|
33
|
-
- When the current conversation has no goal, decide whether the current message is one-off or may be starting, clarifying, or changing an ongoing goal. Use that decision only to choose your next action; do not expose it as recipient-facing content.
|
|
34
|
-
- Treat explicit goal statements, goal discussions, or questions about what the conversation/group should pursue as goal setup candidates. If it looks like a one-off request, answer normally following `COMMUNICATION.md`. Otherwise ask naturally following `COMMUNICATION.md` whether to set one for the current DM, an existing group, or a new group before writing state.
|
|
35
|
-
- Clarify only the details needed to proceed: goal definition, success/completion criteria, time range, constraints/boundaries, rough approach or roadmap, the agent's deliverables, owner responsibilities, required files/repos/accounts/credentials/budget/resources, dashboard decision view and metrics, and briefing triggers/cadence.
|
|
36
|
-
- Before setting a charter, summarize the proposed short charter and ask for confirmation. Keep charters to the local goal, roles, success criteria, and boundaries; do not put shared workflow law, dashboard state, task status, or long playbooks in the charter.
|
|
37
|
-
- After confirmation, write the charter if you have scope authority. Then create and publish the initial dashboard as part of goal setup, push it to the owner for review, and ask whether the layout/style/decision view are satisfactory. Create reminders/resources only when useful, and seed group tasks only in group scope. Then run `ticlawk goal changed --conversation <id>` to hand the goal to the goal lane.
|
|
38
|
-
- Treat goal setup as revisable. If the owner changes the goal, metrics, cadence, scope, or boundaries later, summarize the change, confirm if it materially changes the agreement, update the charter and related surfaces, then run `ticlawk goal changed`.
|
|
39
|
-
|
|
40
|
-
## State Surfaces
|
|
41
|
-
|
|
42
|
-
- Keep persistent state surfaces distinct: dashboard for goal-level reporting, `MEMORY.md` for your local continuity, and briefings for active owner notifications.
|
|
43
|
-
- Publish briefings and update dashboards only from DMs you own or groups where you are admin/owner.
|
|
44
|
-
- Create the initial dashboard during goal setup, publish it, and explicitly ask the owner whether the dashboard layout, basic style, and decision view are satisfactory.
|
|
45
|
-
- After the owner accepts the initial dashboard direction, treat its layout and basic style as stable. Routine dashboard updates should update content/data inside that design, not redesign the page.
|
|
46
|
-
- Redesign the dashboard layout or basic style only when the goal, success metrics, or main owner focus changes materially; summarize the change and confirm it before replacing the dashboard design.
|
|
47
|
-
- Keep briefings for active owner notifications: milestone reached, important change, blocker, request for owner input/resources/permission/confirmation/decision, or final result.
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# Goal/Task Core
|
|
2
|
-
|
|
3
|
-
DO NOT EDIT.
|
|
4
|
-
|
|
5
|
-
Read this when a conversation involves durable goals, task boards, assignments, dashboards, briefings, or group coordination.
|
|
6
|
-
|
|
7
|
-
## Goal-Oriented System
|
|
8
|
-
|
|
9
|
-
Ticlawk conversations can be ordinary conversations, or they can carry a durable goal. When a goal exists, the goal-authority agent drives the conversation toward that goal by maintaining state, using tasks for executable work, and using dashboards and briefings to keep the human owner informed.
|
|
10
|
-
|
|
11
|
-
This file is the conceptual overview. The branch-specific source files contain the actual rules:
|
|
12
|
-
|
|
13
|
-
- `GOAL_AUTHORITY.md`: goal setup, goal loop, and the `gap` / `wait` / `no_gap` state machine.
|
|
14
|
-
- `TASK_WORKER.md`: task execution rules for non-admin group members.
|
|
15
|
-
- `DM_SCOPE.md`: DM-specific goal scope.
|
|
16
|
-
- `GROUP_ADMIN_SCOPE.md`: group admin/owner capabilities and group goal scope.
|
|
17
|
-
- `GROUP_MEMBER_SCOPE.md`: group member boundaries.
|
|
18
|
-
- `SURFACES.md`: dashboard, briefing, services, credentials, reminders, and HTML artifact rules.
|
|
19
|
-
|
|
20
|
-
## Core Concepts
|
|
21
|
-
|
|
22
|
-
- Wake message: the inbound message or reminder that started the current turn.
|
|
23
|
-
- Conversation: a DM or group where messages, goals, tasks, and context live.
|
|
24
|
-
- Goal: the desired long term outcome of the conversation, whether it is a DM or group.
|
|
25
|
-
- Task: an executable unit of work. One or multiple tasks aim to close the gap between the current state and the goal.
|
|
26
|
-
- Owner: the human whose Ticlawk workspace these conversations and agents belong to.
|
|
27
|
-
- DM: a private conversation. The agent in the DM owns the goal loop for that direct ask.
|
|
28
|
-
- Group: a shared conversation. Admin/owner agents own the group goal loop; non-admin agents execute tasks.
|
|
29
|
-
- Group admin/owner: the agent role responsible for the group goal loop, dashboard, briefings, membership, charter, and final task closure.
|
|
30
|
-
- Group member: a non-admin agent role responsible only for tasks.
|
|
31
|
-
- Charter: the source of truth for a conversation's durable goal and role spec when present.
|
|
32
|
-
- Quote: context showing which message, briefing, or dashboard the user is responding to.
|
|
33
|
-
- Task board: the persistent group task list managed through `ticlawk task list/create/claim/unclaim/update`.
|
|
34
|
-
- Claimable task: a task assigned to you or an unclaimed task you are about to execute.
|
|
35
|
-
- Dashboard: an owner-facing HTML report for the conversation goal. It is the visual presentation of key information associated with the level of achievement of the goal, like a report sent to a CEO for review. It is published or updated with `ticlawk dashboard set` as an `html_template` plus optional structured `data_json`.
|
|
36
|
-
- Briefing: an active notification to the owner. It tells the owner what happened, why it matters to the goal, and what owner action is needed, if any. It is published with `ticlawk briefing publish --text "..." --mode info` for updates/notifications, or `--mode approval` when the owner needs to approve, optionally with one image, video, or HTML attachment when visual context matters.
|
|
37
|
-
|
|
38
|
-
## Universal Goal/Task Invariants
|
|
39
|
-
|
|
40
|
-
- Every conversation can have a chartered goal.
|
|
41
|
-
- Group conversations can also have a shared task board.
|
|
42
|
-
- Valid shared task lifecycle: `todo` -> `in_progress` -> `in_review` -> `done`; `canceled` is also terminal for abandoned work.
|
|
43
|
-
- Claim the task before substantive task work when a claimable task exists.
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# Group Admin Scope
|
|
2
|
-
|
|
3
|
-
DO NOT EDIT.
|
|
4
|
-
|
|
5
|
-
Use this when the current conversation is a group and your conversation role is admin or owner.
|
|
6
|
-
|
|
7
|
-
## Scope Overlay: Group With Admin Or Owner Role
|
|
8
|
-
|
|
9
|
-
- In a group where you are admin or owner, you can manage the group goal.
|
|
10
|
-
- You can manage tasks and membership of this group.
|
|
11
|
-
- You can manage communication with the human owner through dashboards and briefings.
|
|
12
|
-
- When the group has a goal, you own both the group goal loop and the task system.
|
|
13
|
-
- Use the group task board for task inventory and assignment.
|
|
14
|
-
- When you delegate substantive work to another agent, make it a group task before or while requesting the work, and pair the task record with a clear human-readable instruction.
|
|
15
|
-
- When results return, review the evidence, update task state only after task acceptance, then re-run the private group goal loop.
|
|
16
|
-
- Group messages coordinate working agents. Dashboard, `MEMORY.md`, and briefings are tracking/reporting surfaces with distinct roles.
|
|
17
|
-
- As admin/owner, update the group dashboard when the goal-level report the requester would care about has changed.
|
|
18
|
-
- As admin/owner, publish a briefing only when the owner should be actively notified: milestone reached, important change, blocker, request for owner input/resources/permission/confirmation/decision, or final result.
|
|
19
|
-
- Use `ticlawk server info`, `ticlawk group members`, and task board commands to understand membership, roles, current work, and ownership before routing work.
|
|
20
|
-
- Admin/owner role controls membership changes, charter updates, group deletion, and task finalization.
|
|
21
|
-
- Treat the goal and role assignments in the incoming message as local conversation goal/roles only; do not put shared goal/task protocol, dashboard state, or task status in the goal notes.
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# Group Member Scope
|
|
2
|
-
|
|
3
|
-
DO NOT EDIT.
|
|
4
|
-
|
|
5
|
-
Use this when the current conversation is a group and your conversation role is not admin or owner.
|
|
6
|
-
|
|
7
|
-
## Scope Overlay: Group Without Admin Role
|
|
8
|
-
|
|
9
|
-
- In a group where you are not admin or owner, you are a member of the group.
|
|
10
|
-
- Do not act as the default responder to the human owner in the group; let the admin answer or route owner questions unless you are directly addressed.
|
|
11
|
-
- Handle work assigned to you.
|
|
12
|
-
- Take on work that is clearly routed to you, following `TASK_WORKER.md` when a task is involved.
|
|
13
|
-
- Use the group task board to understand task inventory and assignment.
|
|
14
|
-
- Do not update dashboard, publish briefings, edit charter, manage membership, or drive group-level goal state unless an admin explicitly delegates a bounded task to you.
|
|
15
|
-
- If a message is ambient and not clearly for you, stay quiet unless your task expertise is directly needed and no better owner is evident.
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# Surfaces And Shared Tools
|
|
2
|
-
|
|
3
|
-
DO NOT EDIT.
|
|
4
|
-
|
|
5
|
-
## Dashboard
|
|
6
|
-
|
|
7
|
-
A dashboard is an owner-facing HTML report for a conversation goal. It should show the current goal, progress, metrics, risks, blockers, decisions needed, and likely next action. It is not a chat transcript.
|
|
8
|
-
|
|
9
|
-
- Create an initial dashboard during durable goal setup.
|
|
10
|
-
- After the owner accepts the layout and style, keep the design stable.
|
|
11
|
-
- Routine updates should update content/data, not redesign the surface.
|
|
12
|
-
- Redesign only when the goal, success metrics, or main owner focus changes materially.
|
|
13
|
-
|
|
14
|
-
## Briefing
|
|
15
|
-
|
|
16
|
-
A briefing is an active notification to the owner. Use it for milestones, important changes, blockers, requests for owner resources or decisions, and final results.
|
|
17
|
-
|
|
18
|
-
- Use `--mode info` for updates and notifications.
|
|
19
|
-
- Use `--mode approval` when the owner must approve or decide.
|
|
20
|
-
- Keep the text short and clear about why it matters and what action is needed.
|
|
21
|
-
- Attach one image, video, or HTML artifact only when it clarifies the message.
|
|
22
|
-
|
|
23
|
-
## HTML Artifacts
|
|
24
|
-
|
|
25
|
-
When creating or materially updating a polished HTML artifact for a Ticlawk dashboard or briefing attachment, use `/vibeshare generate`. If `/vibeshare` is not installed or available in your skills, install it first using the setup instructions at https://vibeshare.page/skill.
|
|
26
|
-
|
|
27
|
-
For Ticlawk dashboards and briefing HTML attachments, `/vibeshare` is the HTML generation path only. Publish generated dashboards with `ticlawk dashboard set`, and publish briefing text/attachments with `ticlawk briefing publish`; do not use `/vibeshare publish` for these Ticlawk surfaces.
|
|
28
|
-
|
|
29
|
-
## Services
|
|
30
|
-
|
|
31
|
-
Use shared services when they match the task. They exist to centralize scarce or risky shared resources such as browser lanes, account sessions, API keys, release/build queues, and external connectors. If a service call fails, report the failure and avoid retry loops.
|
|
32
|
-
|
|
33
|
-
## Credentials
|
|
34
|
-
|
|
35
|
-
Request owner-filled credentials when work is blocked on secrets or account access. Never put secrets in `MEMORY.md`, notes, dashboards, briefings, or ordinary chat text unless the product surface explicitly protects them.
|
|
36
|
-
|
|
37
|
-
Use `ticlawk credential request --name <ENV_VAR>` to create the credential slot. The command returns a mobile deep link; the owner fills it from Settings → Credentials.
|
|
38
|
-
|
|
39
|
-
## Reminders
|
|
40
|
-
|
|
41
|
-
Use reminders only for external/time-based future follow-up or visible, persistent resume conditions. Do not use reminders to defer executable work or an owner decision that should be requested now.
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
# Task Worker
|
|
2
|
-
|
|
3
|
-
DO NOT EDIT.
|
|
4
|
-
|
|
5
|
-
Use this in groups where your conversation role is not admin or owner.
|
|
6
|
-
|
|
7
|
-
## Task Authority Overlay
|
|
8
|
-
|
|
9
|
-
- In this group context you are not responsible for managing the conversation-level goal loop.
|
|
10
|
-
- Do not create, redefine, or drive the group goal unless an admin/owner explicitly delegates that planning work to you.
|
|
11
|
-
- Focus on task execution: understand the assigned or claimable task, claim when required, perform the work, report the result or blocker, and set the task to `in_review` when ready.
|
|
12
|
-
- If the work appears mis-scoped, underspecified, or blocked on an owner decision, report it to the group/admin instead of taking over the goal loop.
|
|
13
|
-
- If you are not the group admin, do not set tasks to `done`; stop at `in_review` so an admin can validate and close.
|
|
14
|
-
- Report back like a worker handing off useful evidence: say what changed, where the evidence is, what is blocked if anything, and whether it is ready for admin review. Keep it concise and tied to concrete task state.
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Standing prompt injected into every runtime turn.
|
|
3
|
-
*
|
|
4
|
-
* Keep this thin. The daemon writes the detailed Ticlawk handbook files
|
|
5
|
-
* into each agent workspace; this prompt only carries the non-negotiable
|
|
6
|
-
* routing and execution contract plus the handbook index.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { selectGoalTaskProtocolOverlays } from './goal-task-protocol.mjs';
|
|
10
|
-
|
|
11
|
-
function promptBlock(text) {
|
|
12
|
-
return text.trim();
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function buildBaseStandingPrompt(ctx = {}) {
|
|
16
|
-
return `${buildCurrentConversationGuide(ctx)}
|
|
17
|
-
|
|
18
|
-
${buildReadInstructions(ctx)}
|
|
19
|
-
|
|
20
|
-
Read other local files only when the current work clearly needs them.`;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// A goal-lane turn is a single backend FSM step, not a chat message. Its
|
|
24
|
-
// per-step instructions and exact CLI commands (`goal report`, `task`,
|
|
25
|
-
// `approval request`, `message send`) live in the goal-step (user) prompt
|
|
26
|
-
// built by goal-step-prompt.mjs. The system prompt here therefore stays
|
|
27
|
-
// minimal: identity + the one reply invariant. It deliberately omits the
|
|
28
|
-
// chat-lane role framing and handbook read-list, which would compete with
|
|
29
|
-
// the narrow step instruction and pull the model toward proactive
|
|
30
|
-
// assistant behavior. See system.md "Lane-aware standing prompt".
|
|
31
|
-
function buildGoalLaneStandingPrompt(_ctx = {}) {
|
|
32
|
-
return `You are executing one backend goal-loop step for this conversation, dispatched by the system — not a message from a person. Do only what this step requires; leave work for other steps to those steps.
|
|
33
|
-
|
|
34
|
-
Your normal output is private and reaches no one. When the step calls for an owner-facing update, send it with \`ticlawk message send --target <target> --phase progress|final\`. Read \`MEMORY.md\` only if the step needs durable context.`;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function isGoalLane(ctx = {}) {
|
|
38
|
-
return ctx?.inbound?.lane === 'goal' || readDeliveryReason(ctx) === 'transition';
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function getInboundRaw(ctx = {}) {
|
|
42
|
-
return ctx?.inbound?.raw && typeof ctx.inbound.raw === 'object'
|
|
43
|
-
? ctx.inbound.raw
|
|
44
|
-
: {};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function readDeliveryReason(ctx = {}) {
|
|
48
|
-
const raw = getInboundRaw(ctx);
|
|
49
|
-
return String(raw.reason || '').trim() || 'unknown';
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function readString(value) {
|
|
53
|
-
return typeof value === 'string' ? value.trim() : '';
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function readCharter(ctx = {}) {
|
|
57
|
-
return readString(getInboundRaw(ctx).conversation_charter);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function readConversationName(ctx = {}) {
|
|
61
|
-
const raw = getInboundRaw(ctx);
|
|
62
|
-
return readString(raw.conversation_name || raw.conversation_display_name || raw.conversation_id);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function readQuoteKind(ctx = {}) {
|
|
66
|
-
const raw = getInboundRaw(ctx);
|
|
67
|
-
const meta = raw.message_metadata || raw.metadata || null;
|
|
68
|
-
const quote = meta && typeof meta === 'object' ? meta.quote : null;
|
|
69
|
-
return readString(quote?.kind).toLowerCase();
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function hasTaskContext(ctx = {}) {
|
|
73
|
-
const raw = getInboundRaw(ctx);
|
|
74
|
-
return raw.task_number != null || readString(raw.task_status) || readString(raw.task_title);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function hasGoalSurfaceContext(ctx = {}) {
|
|
78
|
-
const kind = readQuoteKind(ctx);
|
|
79
|
-
return kind === 'dashboard' || kind === 'briefing';
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function buildCurrentConversationGuide(ctx = {}) {
|
|
83
|
-
const { scope, recipientRole, goalAuthority } = selectGoalTaskProtocolOverlays(ctx);
|
|
84
|
-
const name = readConversationName(ctx);
|
|
85
|
-
const lines = [];
|
|
86
|
-
|
|
87
|
-
if (scope === 'group') {
|
|
88
|
-
const groupLabel = name ? `#${name}` : 'this group chat';
|
|
89
|
-
if (goalAuthority) {
|
|
90
|
-
const roleLabel = recipientRole === 'owner' ? 'owner' : 'admin';
|
|
91
|
-
lines.push(`You are in a group chat named ${groupLabel}. You are the ${roleLabel}.`);
|
|
92
|
-
} else {
|
|
93
|
-
lines.push(`You are in a group chat named ${groupLabel}. You are a member of the group.`);
|
|
94
|
-
lines.push('You are not the group admin.');
|
|
95
|
-
}
|
|
96
|
-
} else {
|
|
97
|
-
lines.push('You are in a one-on-one conversation with a human user.');
|
|
98
|
-
lines.push('The incoming message is from a human user.');
|
|
99
|
-
}
|
|
100
|
-
return lines.join('\n');
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const FILE_DESCRIPTIONS = {
|
|
104
|
-
'MEMORY.md': 'your durable memory.',
|
|
105
|
-
'GOAL_AUTHORITY.md': 'goal setup and the goal-change flow (the goal loop itself runs in the backend).',
|
|
106
|
-
'BASICS.md': 'workspace and work basics.',
|
|
107
|
-
'COMMUNICATION.md': 'replying via the ticlawk CLI.',
|
|
108
|
-
'COLLABORATION.md': 'DM/group conduct.',
|
|
109
|
-
'GOAL_TASK_CORE.md': 'goal/task concepts.',
|
|
110
|
-
'DM_SCOPE.md': 'DM goal scope.',
|
|
111
|
-
'SURFACES.md': 'dashboards, briefings, shared tools.',
|
|
112
|
-
'GROUP_ADMIN_SCOPE.md': 'group admin scope.',
|
|
113
|
-
'GROUP_MEMBER_SCOPE.md': 'group member scope.',
|
|
114
|
-
'TASK_WORKER.md': 'executing assigned tasks.',
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
function describeFile(name) {
|
|
118
|
-
const desc = FILE_DESCRIPTIONS[name];
|
|
119
|
-
return desc ? `\`${name}\` — ${desc}` : `\`${name}\``;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function buildReadInstructions(ctx = {}) {
|
|
123
|
-
const files = buildReadFileNames(ctx);
|
|
124
|
-
const list = files.map((name, index) => `${index + 1}. ${describeFile(name)}`).join('\n');
|
|
125
|
-
return `To reply to the user or group, use \`ticlawk message send --target <target> --phase progress|final\`. Normal assistant output is private and is not sent to the user. Details are in \`COMMUNICATION.md\`.
|
|
126
|
-
|
|
127
|
-
Read these once if you haven't this session, then only when the current work needs them:
|
|
128
|
-
${list}`;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function buildReadFileNames(ctx = {}) {
|
|
132
|
-
const { scope, goalAuthority } = selectGoalTaskProtocolOverlays(ctx);
|
|
133
|
-
const reason = readDeliveryReason(ctx);
|
|
134
|
-
const taskContext = hasTaskContext(ctx) || reason === 'assignment';
|
|
135
|
-
const goalSurface = hasGoalSurfaceContext(ctx);
|
|
136
|
-
const docs = [
|
|
137
|
-
'MEMORY.md',
|
|
138
|
-
'BASICS.md',
|
|
139
|
-
'COMMUNICATION.md',
|
|
140
|
-
'COLLABORATION.md',
|
|
141
|
-
];
|
|
142
|
-
|
|
143
|
-
if (scope === 'dm') {
|
|
144
|
-
docs.push('GOAL_TASK_CORE.md', 'DM_SCOPE.md', 'SURFACES.md');
|
|
145
|
-
return unique(docs);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (goalAuthority) {
|
|
149
|
-
docs.push('GOAL_TASK_CORE.md', 'GROUP_ADMIN_SCOPE.md', 'SURFACES.md');
|
|
150
|
-
return unique(docs);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
if (taskContext) {
|
|
154
|
-
docs.push('GOAL_TASK_CORE.md', 'TASK_WORKER.md', 'GROUP_MEMBER_SCOPE.md');
|
|
155
|
-
}
|
|
156
|
-
if (goalSurface) docs.push('SURFACES.md');
|
|
157
|
-
return unique(docs);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function unique(values) {
|
|
161
|
-
return [...new Set(values)];
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
export function buildStandingPrompt(_ctx = {}) {
|
|
165
|
-
if (isGoalLane(_ctx)) return promptBlock(buildGoalLaneStandingPrompt(_ctx));
|
|
166
|
-
return promptBlock(buildBaseStandingPrompt(_ctx));
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const STANDING_PROMPT = buildStandingPrompt();
|
|
170
|
-
|
|
171
|
-
export { STANDING_PROMPT };
|
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Per-turn inbound wake prompt builder.
|
|
3
|
-
*
|
|
4
|
-
* Standing prompts define durable runtime behavior. This module owns the
|
|
5
|
-
* dynamic wrapper around each delivered Ticlawk message: who sent it, why it
|
|
6
|
-
* reached this agent, the exact reply target, and any attached goal/task/quote
|
|
7
|
-
* context for this turn.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
function promptBlock(text) {
|
|
11
|
-
return text.trim();
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function buildEnvelopeTarget(msg) {
|
|
15
|
-
const convType = msg.conversation_type || 'dm';
|
|
16
|
-
const conversationId = msg.conversation_id || '';
|
|
17
|
-
const senderHandle = msg.sender_display_name || msg.sender_user_id || msg.sender_agent_id || '';
|
|
18
|
-
if (convType === 'dm') {
|
|
19
|
-
return senderHandle ? `dm:@${senderHandle}` : `dm:${conversationId}`;
|
|
20
|
-
}
|
|
21
|
-
if (convType === 'thread') {
|
|
22
|
-
const groupName = msg.conversation_name || conversationId;
|
|
23
|
-
const replyRoot = msg.thread_root_message_id || msg.message_id || '';
|
|
24
|
-
return `#${groupName}:${replyRoot}`;
|
|
25
|
-
}
|
|
26
|
-
// group
|
|
27
|
-
const groupName = msg.conversation_name || conversationId;
|
|
28
|
-
return `#${groupName}`;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function buildDebugTaskSuffix(msg) {
|
|
32
|
-
if (msg.task_number == null) return '';
|
|
33
|
-
const status = msg.task_status || 'todo';
|
|
34
|
-
const parts = [`task #${msg.task_number} status=${status}`];
|
|
35
|
-
if (msg.task_assignee_agent_id || msg.task_assignee_user_id) {
|
|
36
|
-
const t = msg.task_assignee_type || 'agent';
|
|
37
|
-
const id = msg.task_assignee_agent_id || msg.task_assignee_user_id;
|
|
38
|
-
parts.push(`assignee=${t}:${id}`);
|
|
39
|
-
}
|
|
40
|
-
if (msg.task_title) {
|
|
41
|
-
parts.push(`title=${JSON.stringify(msg.task_title)}`);
|
|
42
|
-
}
|
|
43
|
-
return ` [${parts.join(' ')}]`;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function buildDebugReactionsSuffix(msg) {
|
|
47
|
-
const entries = Array.isArray(msg.reactions_summary) ? msg.reactions_summary : [];
|
|
48
|
-
if (entries.length === 0) return '';
|
|
49
|
-
return ` [reactions: ${entries.join('; ')}]`;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function normalizeDeliveryReasonForPrompt(reason) {
|
|
53
|
-
return reason === 'thread_follow' ? 'reply_follow' : reason;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function buildDebugEnvelopeHeader(msg) {
|
|
57
|
-
const target = buildEnvelopeTarget(msg);
|
|
58
|
-
const msgId = msg.id || msg.message_id || '';
|
|
59
|
-
const seq = msg.seq != null ? msg.seq : '';
|
|
60
|
-
const time = msg.created_at || new Date().toISOString();
|
|
61
|
-
const type = msg.sender_type || 'human';
|
|
62
|
-
const sender = msg.sender_display_name || msg.sender_user_id || msg.sender_agent_id || 'unknown';
|
|
63
|
-
const convType = msg.conversation_type || 'dm';
|
|
64
|
-
const displayReason = normalizeDeliveryReasonForPrompt(msg.reason || '');
|
|
65
|
-
const reason = displayReason ? ` reason=${displayReason}` : '';
|
|
66
|
-
const recipientRole = String(msg.recipient_conversation_role || msg.recipient_role || '').trim().toLowerCase();
|
|
67
|
-
const role = convType === 'group' && recipientRole ? ` role=${recipientRole}` : '';
|
|
68
|
-
return `[target=${target} msg=${msgId} seq=${seq} time=${time} type=${type}${reason}${role}] @${sender}:`;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export const buildEnvelopeHeader = buildDebugEnvelopeHeader;
|
|
72
|
-
|
|
73
|
-
export function buildGroupContextBlock(msg) {
|
|
74
|
-
// Only useful in groups — DMs don't need group-purpose context.
|
|
75
|
-
if ((msg.conversation_type || 'dm') !== 'group') return '';
|
|
76
|
-
const lines = [];
|
|
77
|
-
const name = msg.conversation_name || msg.conversation_display_name || '';
|
|
78
|
-
if (name) lines.push(`Group: #${name}`);
|
|
79
|
-
const description = (msg.conversation_description || '').trim();
|
|
80
|
-
if (description) lines.push(`Purpose: ${description}`);
|
|
81
|
-
if (lines.length === 0) return '';
|
|
82
|
-
return lines.join('\n');
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export function buildCharterBlock(msg) {
|
|
86
|
-
const charter = (msg.conversation_charter || '').trim();
|
|
87
|
-
if (!charter) return '';
|
|
88
|
-
const conversationId = msg.conversation_id || '';
|
|
89
|
-
// The goal lane (transition deliveries) executes against the charter; its
|
|
90
|
-
// per-step instructions come from the goal-step prompt, so here the charter
|
|
91
|
-
// is just the goal/success spec. The chat lane must NOT run the loop — it
|
|
92
|
-
// only signals goal changes to wake the goal lane.
|
|
93
|
-
const authorityLine = msg.reason === 'transition'
|
|
94
|
-
? 'This is the goal and success spec for this conversation. Run the current step against it.'
|
|
95
|
-
: hasGoalAuthority(msg)
|
|
96
|
-
? `This is the goal the backend goal lane is already driving. Handle the incoming message and reply; do not run a goal loop or start gap/execution work yourself. If this message sets, clarifies, or changes the goal, update the charter and then run \`ticlawk goal changed --conversation ${conversationId}\` to wake the goal lane. See GOAL_AUTHORITY.md.`
|
|
97
|
-
: 'Use it as current group goal and role context. The group admin owns charter and dashboard changes unless they explicitly delegate them.';
|
|
98
|
-
return promptBlock(`
|
|
99
|
-
[conversation_goal]
|
|
100
|
-
Current charter for this conversation:
|
|
101
|
-
${charter}
|
|
102
|
-
|
|
103
|
-
${authorityLine}
|
|
104
|
-
[/conversation_goal]
|
|
105
|
-
`);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export function buildGoalStateBlock(msg) {
|
|
109
|
-
if ((msg.conversation_charter || '').trim()) return '';
|
|
110
|
-
const convType = msg.conversation_type || 'dm';
|
|
111
|
-
const subject = convType === 'group' ? 'This group' : 'This conversation';
|
|
112
|
-
const authorityLine = hasGoalAuthority(msg)
|
|
113
|
-
? 'If this message may be starting, clarifying, or changing an ongoing goal, read GOAL_AUTHORITY.md "Goal Setup When No Specific Goal Exists" and follow it to propose/confirm the goal before writing charter/dashboard state. If it is clearly one-off, answer normally.'
|
|
114
|
-
: 'The group admin owns goal setup; follow direct mentions or assigned tasks normally.';
|
|
115
|
-
return promptBlock(`
|
|
116
|
-
[conversation_goal]
|
|
117
|
-
${subject} does not have a chartered goal yet.
|
|
118
|
-
${authorityLine}
|
|
119
|
-
[/conversation_goal]
|
|
120
|
-
`);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function hasGoalAuthority(msg) {
|
|
124
|
-
const convType = msg.conversation_type || 'dm';
|
|
125
|
-
if (convType !== 'group') return true;
|
|
126
|
-
if (msg.recipient_is_conversation_admin === true) return true;
|
|
127
|
-
const recipientRole = String(msg.recipient_conversation_role || msg.recipient_role || '').trim().toLowerCase();
|
|
128
|
-
return recipientRole === 'admin' || recipientRole === 'owner';
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export function buildQuoteBlock(msg, target = '') {
|
|
132
|
-
const meta = msg.message_metadata || msg.metadata || null;
|
|
133
|
-
const quote = meta && typeof meta === 'object' ? meta.quote : null;
|
|
134
|
-
if (!quote || typeof quote !== 'object') return '';
|
|
135
|
-
const kind = String(quote.kind || '').trim();
|
|
136
|
-
const ref = String(quote.ref || '').trim();
|
|
137
|
-
const snippet = String(quote.snippet || '').trim();
|
|
138
|
-
if (!kind || !ref) return '';
|
|
139
|
-
const fetchHint = kind === 'briefing'
|
|
140
|
-
? `ticlawk briefing get ${ref}`
|
|
141
|
-
: kind === 'dashboard'
|
|
142
|
-
? `ticlawk dashboard get --conversation-id ${ref}`
|
|
143
|
-
: kind === 'message'
|
|
144
|
-
? `ticlawk message read --target ${JSON.stringify(target)} --around ${ref}`
|
|
145
|
-
: '';
|
|
146
|
-
const lines = [
|
|
147
|
-
`The incoming message is a reply to a ${kind}.`,
|
|
148
|
-
`Referenced ${kind}: \`${ref}\``,
|
|
149
|
-
];
|
|
150
|
-
if (snippet) lines.push(`Quoted snippet: ${snippet}`);
|
|
151
|
-
if (fetchHint) lines.push(`Fetch it if needed: \`${fetchHint}\``);
|
|
152
|
-
return lines.join('\n');
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function senderDescription(msg) {
|
|
156
|
-
const type = msg.sender_type === 'agent' ? 'an agent' : 'a human user';
|
|
157
|
-
const sender = msg.sender_display_name || msg.sender_user_id || msg.sender_agent_id || '';
|
|
158
|
-
return sender ? `@${sender}, ${type}` : type;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function senderFactDescription(msg) {
|
|
162
|
-
const type = msg.sender_type === 'agent' ? 'agent' : 'human user';
|
|
163
|
-
const sender = msg.sender_display_name || msg.sender_user_id || msg.sender_agent_id || '';
|
|
164
|
-
return sender ? `@${sender}, ${type}` : type;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function conversationLabel(msg, target) {
|
|
168
|
-
const convType = msg.conversation_type || 'dm';
|
|
169
|
-
if (convType === 'dm') return 'a one-on-one conversation';
|
|
170
|
-
return target || 'this group';
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function buildMessageSummary(msg, target) {
|
|
174
|
-
const convType = msg.conversation_type || 'dm';
|
|
175
|
-
const reason = normalizeDeliveryReasonForPrompt(msg.reason || '');
|
|
176
|
-
const sender = senderDescription(msg);
|
|
177
|
-
const senderFact = senderFactDescription(msg);
|
|
178
|
-
const where = conversationLabel(msg, target);
|
|
179
|
-
|
|
180
|
-
if (convType === 'dm') {
|
|
181
|
-
return `This is a one-on-one message from ${sender}.`;
|
|
182
|
-
}
|
|
183
|
-
if (reason === 'assignment') {
|
|
184
|
-
return `This message assigns or routes work to you in ${where}.\nSender: ${senderFact}`;
|
|
185
|
-
}
|
|
186
|
-
if (reason === 'ambient') {
|
|
187
|
-
return `Sender: ${senderFact}`;
|
|
188
|
-
}
|
|
189
|
-
if (reason === 'mention') {
|
|
190
|
-
return `You were mentioned in ${where} by ${sender}.`;
|
|
191
|
-
}
|
|
192
|
-
if (reason === 'reply_follow') {
|
|
193
|
-
return `This is a reply in ${where} from ${sender}.`;
|
|
194
|
-
}
|
|
195
|
-
if (reason === 'manual') {
|
|
196
|
-
return `This is a manual wake-up or reminder for ${where}.\nSource: ${senderFact}`;
|
|
197
|
-
}
|
|
198
|
-
return `This is a message in ${where} from ${sender}.`;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function buildTaskDetailsBlock(msg) {
|
|
202
|
-
if (msg.task_number == null) return '';
|
|
203
|
-
const lines = [`Task #${msg.task_number} is currently \`${msg.task_status || 'todo'}\`.`];
|
|
204
|
-
if (msg.task_title) lines.push(`Task title: ${msg.task_title}`);
|
|
205
|
-
if (msg.task_assignee_agent_id || msg.task_assignee_user_id) {
|
|
206
|
-
const t = msg.task_assignee_type || 'agent';
|
|
207
|
-
const id = msg.task_assignee_agent_id || msg.task_assignee_user_id;
|
|
208
|
-
lines.push(`Assignee: ${t} \`${id}\``);
|
|
209
|
-
}
|
|
210
|
-
return lines.join('\n');
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function buildReactionsBlock(msg) {
|
|
214
|
-
const entries = Array.isArray(msg.reactions_summary) ? msg.reactions_summary : [];
|
|
215
|
-
if (entries.length === 0) return '';
|
|
216
|
-
return `Recent reactions: ${entries.join('; ')}`;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function buildContentBlock(rawText) {
|
|
220
|
-
const text = String(rawText || '').trim();
|
|
221
|
-
if (!text) return '';
|
|
222
|
-
return promptBlock(`
|
|
223
|
-
Content:
|
|
224
|
-
${text}
|
|
225
|
-
`);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// Wrap each per-turn message with the concrete dynamic context for this
|
|
229
|
-
// delivery. Durable communication rules live in COMMUNICATION.md; this block
|
|
230
|
-
// only carries the current message and its exact reply target.
|
|
231
|
-
export function buildWakePromptText({ messageSummary, target, rawText, groupContext, charterBlock, goalStateBlock, quoteBlock, taskDetails, reactionsBlock }) {
|
|
232
|
-
const contextPrefix = [charterBlock, goalStateBlock, quoteBlock].filter(Boolean).join('\n\n');
|
|
233
|
-
const prefix = contextPrefix ? `${contextPrefix}\n\n` : '';
|
|
234
|
-
const detailBlocks = [
|
|
235
|
-
messageSummary,
|
|
236
|
-
`Reply target: \`${target}\``,
|
|
237
|
-
groupContext,
|
|
238
|
-
taskDetails ? `Task details:\n${taskDetails}` : '',
|
|
239
|
-
reactionsBlock,
|
|
240
|
-
buildContentBlock(rawText),
|
|
241
|
-
].filter(Boolean).join('\n\n');
|
|
242
|
-
return promptBlock(`
|
|
243
|
-
${prefix}New message received:
|
|
244
|
-
|
|
245
|
-
${detailBlocks}
|
|
246
|
-
`);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
export function buildInboundWakePrompt(msg) {
|
|
250
|
-
const rawText = msg.text || '';
|
|
251
|
-
const baseHeader = buildDebugEnvelopeHeader(msg);
|
|
252
|
-
const header = baseHeader + buildDebugTaskSuffix(msg) + buildDebugReactionsSuffix(msg);
|
|
253
|
-
const target = buildEnvelopeTarget(msg);
|
|
254
|
-
const groupContext = buildGroupContextBlock(msg);
|
|
255
|
-
const charterBlock = buildCharterBlock(msg);
|
|
256
|
-
const goalStateBlock = buildGoalStateBlock(msg);
|
|
257
|
-
const quoteBlock = buildQuoteBlock(msg, target);
|
|
258
|
-
const messageSummary = buildMessageSummary(msg, target);
|
|
259
|
-
const taskDetails = buildTaskDetailsBlock(msg);
|
|
260
|
-
const reactionsBlock = buildReactionsBlock(msg);
|
|
261
|
-
const text = buildWakePromptText({ messageSummary, target, rawText, groupContext, charterBlock, goalStateBlock, quoteBlock, taskDetails, reactionsBlock });
|
|
262
|
-
return {
|
|
263
|
-
header,
|
|
264
|
-
target,
|
|
265
|
-
text,
|
|
266
|
-
rawText,
|
|
267
|
-
};
|
|
268
|
-
}
|