the-grid-cc 1.1.0
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/LICENSE +21 -0
- package/README.md +206 -0
- package/agents/grid-executor.md +62 -0
- package/agents/grid-guard.md +72 -0
- package/agents/grid-planner.md +70 -0
- package/agents/grid-recognizer.md +100 -0
- package/bin/install.js +276 -0
- package/cli/__init__.py +7 -0
- package/cli/main.py +385 -0
- package/commands/grid/VERSION +1 -0
- package/commands/grid/help.md +54 -0
- package/commands/grid/init.md +51 -0
- package/commands/grid/mcp.md +159 -0
- package/commands/grid.md +12 -0
- package/config/grid.yaml +46 -0
- package/core/__init__.py +45 -0
- package/core/block.py +207 -0
- package/core/cluster.py +228 -0
- package/core/disc.py +254 -0
- package/core/energy.py +267 -0
- package/core/grid.py +326 -0
- package/core/io_tower.py +242 -0
- package/core/program.py +268 -0
- package/core/recognizer.py +294 -0
- package/core/thread.py +180 -0
- package/package.json +37 -0
- package/templates/__init__.py +14 -0
- package/templates/status.py +223 -0
- package/templates/welcome.py +101 -0
package/core/io_tower.py
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"""
|
|
2
|
+
I/O Tower - Human checkpoints where Users connect to The Grid.
|
|
3
|
+
|
|
4
|
+
"The I/O Tower is sacred. It's where we commune with the Users."
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from typing import Optional, Any, Callable
|
|
10
|
+
from enum import Enum
|
|
11
|
+
import uuid
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CheckpointReason(Enum):
|
|
15
|
+
"""Reason for I/O Tower checkpoint."""
|
|
16
|
+
FISSION_DEPTH = "fission_depth"
|
|
17
|
+
BEFORE_COMMIT = "before_commit"
|
|
18
|
+
ERROR_OCCURRED = "error_occurred"
|
|
19
|
+
LOW_ENERGY = "low_energy"
|
|
20
|
+
RECOGNIZER_FAILED = "recognizer_failed"
|
|
21
|
+
USER_REQUESTED = "user_requested"
|
|
22
|
+
DECISION_REQUIRED = "decision_required"
|
|
23
|
+
REVIEW_REQUIRED = "review_required"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class UserDecision(Enum):
|
|
27
|
+
"""User decision at checkpoint."""
|
|
28
|
+
AUTHORIZE = "authorize" # Allow action to proceed
|
|
29
|
+
DENY = "deny" # Block action
|
|
30
|
+
MODIFY = "modify" # Modify before proceeding
|
|
31
|
+
MERGE = "merge" # Merge operations (for fission)
|
|
32
|
+
ABORT = "abort" # Abort entire operation
|
|
33
|
+
DEFER = "defer" # Defer decision
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class CheckpointOption:
|
|
38
|
+
"""An option presented at a checkpoint."""
|
|
39
|
+
key: str
|
|
40
|
+
label: str
|
|
41
|
+
description: str
|
|
42
|
+
decision: UserDecision
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class CheckpointContext:
|
|
47
|
+
"""Context for an I/O Tower checkpoint."""
|
|
48
|
+
reason: CheckpointReason
|
|
49
|
+
program_id: str
|
|
50
|
+
program_name: str
|
|
51
|
+
depth: int
|
|
52
|
+
energy_remaining: int
|
|
53
|
+
energy_allocated: int
|
|
54
|
+
pending_actions: list[str] = field(default_factory=list)
|
|
55
|
+
details: dict = field(default_factory=dict)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class CheckpointResult:
|
|
60
|
+
"""Result of a checkpoint interaction."""
|
|
61
|
+
decision: UserDecision
|
|
62
|
+
timestamp: datetime
|
|
63
|
+
user_input: Optional[str] = None
|
|
64
|
+
modifications: dict = field(default_factory=dict)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class IOTower:
|
|
68
|
+
"""
|
|
69
|
+
I/O Tower - Human checkpoint interface.
|
|
70
|
+
|
|
71
|
+
Provides human checkpoints at critical moments:
|
|
72
|
+
- Fission depth >= configured threshold
|
|
73
|
+
- Before commits
|
|
74
|
+
- On errors
|
|
75
|
+
- Energy running low
|
|
76
|
+
- Recognizer flags issues
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
# Default options for common checkpoint types
|
|
80
|
+
DEFAULT_OPTIONS = {
|
|
81
|
+
CheckpointReason.FISSION_DEPTH: [
|
|
82
|
+
CheckpointOption("A", "Authorize", "Allow fission to continue", UserDecision.AUTHORIZE),
|
|
83
|
+
CheckpointOption("M", "Merge", "Combine into single Program (saves Energy)", UserDecision.MERGE),
|
|
84
|
+
CheckpointOption("D", "Deny", "Block spawning, continue with current Program", UserDecision.DENY),
|
|
85
|
+
CheckpointOption("V", "View Disc", "See full context before deciding", UserDecision.DEFER),
|
|
86
|
+
],
|
|
87
|
+
CheckpointReason.BEFORE_COMMIT: [
|
|
88
|
+
CheckpointOption("C", "Commit", "Proceed with commit", UserDecision.AUTHORIZE),
|
|
89
|
+
CheckpointOption("R", "Review", "Review changes first", UserDecision.DEFER),
|
|
90
|
+
CheckpointOption("M", "Modify", "Modify commit message/contents", UserDecision.MODIFY),
|
|
91
|
+
CheckpointOption("A", "Abort", "Cancel commit", UserDecision.ABORT),
|
|
92
|
+
],
|
|
93
|
+
CheckpointReason.ERROR_OCCURRED: [
|
|
94
|
+
CheckpointOption("R", "Retry", "Retry the operation", UserDecision.AUTHORIZE),
|
|
95
|
+
CheckpointOption("S", "Skip", "Skip this operation", UserDecision.DENY),
|
|
96
|
+
CheckpointOption("A", "Abort", "Abort the entire process", UserDecision.ABORT),
|
|
97
|
+
CheckpointOption("D", "Debug", "View debug information", UserDecision.DEFER),
|
|
98
|
+
],
|
|
99
|
+
CheckpointReason.LOW_ENERGY: [
|
|
100
|
+
CheckpointOption("C", "Continue", "Continue with remaining energy", UserDecision.AUTHORIZE),
|
|
101
|
+
CheckpointOption("A", "Allocate", "Allocate more energy", UserDecision.MODIFY),
|
|
102
|
+
CheckpointOption("P", "Pause", "Pause and save state", UserDecision.DEFER),
|
|
103
|
+
CheckpointOption("S", "Stop", "Stop execution", UserDecision.ABORT),
|
|
104
|
+
],
|
|
105
|
+
CheckpointReason.RECOGNIZER_FAILED: [
|
|
106
|
+
CheckpointOption("F", "Fix", "Fix issues and retry", UserDecision.MODIFY),
|
|
107
|
+
CheckpointOption("O", "Override", "Override and continue", UserDecision.AUTHORIZE),
|
|
108
|
+
CheckpointOption("R", "Review", "Review issues in detail", UserDecision.DEFER),
|
|
109
|
+
CheckpointOption("A", "Abort", "Abort due to validation failure", UserDecision.ABORT),
|
|
110
|
+
],
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
def __init__(
|
|
114
|
+
self,
|
|
115
|
+
require_at_depth: int = 3,
|
|
116
|
+
require_before_commit: bool = True,
|
|
117
|
+
require_on_error: bool = True,
|
|
118
|
+
require_on_low_energy: bool = True,
|
|
119
|
+
timeout_seconds: int = 300,
|
|
120
|
+
):
|
|
121
|
+
self.require_at_depth = require_at_depth
|
|
122
|
+
self.require_before_commit = require_before_commit
|
|
123
|
+
self.require_on_error = require_on_error
|
|
124
|
+
self.require_on_low_energy = require_on_low_energy
|
|
125
|
+
self.timeout_seconds = timeout_seconds
|
|
126
|
+
|
|
127
|
+
# Checkpoint history
|
|
128
|
+
self.checkpoints: list[tuple[CheckpointContext, CheckpointResult]] = []
|
|
129
|
+
|
|
130
|
+
# Callback for user input (to be set by CLI)
|
|
131
|
+
self.input_handler: Optional[Callable[[CheckpointContext, list[CheckpointOption]], CheckpointResult]] = None
|
|
132
|
+
|
|
133
|
+
def should_checkpoint(
|
|
134
|
+
self,
|
|
135
|
+
reason: CheckpointReason,
|
|
136
|
+
depth: int = 0,
|
|
137
|
+
energy_percentage: float = 100.0,
|
|
138
|
+
) -> bool:
|
|
139
|
+
"""Determine if a checkpoint is required."""
|
|
140
|
+
if reason == CheckpointReason.FISSION_DEPTH:
|
|
141
|
+
return depth >= self.require_at_depth
|
|
142
|
+
elif reason == CheckpointReason.BEFORE_COMMIT:
|
|
143
|
+
return self.require_before_commit
|
|
144
|
+
elif reason == CheckpointReason.ERROR_OCCURRED:
|
|
145
|
+
return self.require_on_error
|
|
146
|
+
elif reason == CheckpointReason.LOW_ENERGY:
|
|
147
|
+
return self.require_on_low_energy and energy_percentage < 10
|
|
148
|
+
elif reason == CheckpointReason.RECOGNIZER_FAILED:
|
|
149
|
+
return True # Always checkpoint on validation failure
|
|
150
|
+
elif reason == CheckpointReason.USER_REQUESTED:
|
|
151
|
+
return True
|
|
152
|
+
return False
|
|
153
|
+
|
|
154
|
+
def checkpoint(
|
|
155
|
+
self,
|
|
156
|
+
context: CheckpointContext,
|
|
157
|
+
options: list[CheckpointOption] = None,
|
|
158
|
+
) -> CheckpointResult:
|
|
159
|
+
"""
|
|
160
|
+
Trigger a checkpoint and await user decision.
|
|
161
|
+
|
|
162
|
+
In actual use, this would pause execution and present
|
|
163
|
+
options to the user through the CLI.
|
|
164
|
+
"""
|
|
165
|
+
if options is None:
|
|
166
|
+
options = self.DEFAULT_OPTIONS.get(context.reason, [])
|
|
167
|
+
|
|
168
|
+
# If we have an input handler, use it
|
|
169
|
+
if self.input_handler:
|
|
170
|
+
result = self.input_handler(context, options)
|
|
171
|
+
else:
|
|
172
|
+
# Default: authorize (for testing/non-interactive)
|
|
173
|
+
result = CheckpointResult(
|
|
174
|
+
decision=UserDecision.AUTHORIZE,
|
|
175
|
+
timestamp=datetime.now(),
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
self.checkpoints.append((context, result))
|
|
179
|
+
return result
|
|
180
|
+
|
|
181
|
+
def render_checkpoint(
|
|
182
|
+
self,
|
|
183
|
+
context: CheckpointContext,
|
|
184
|
+
options: list[CheckpointOption],
|
|
185
|
+
) -> str:
|
|
186
|
+
"""Render checkpoint display."""
|
|
187
|
+
lines = [
|
|
188
|
+
"╔" + "═" * 77 + "╗",
|
|
189
|
+
"║ ⛯ I/O TOWER - Human Input Required" + " " * 40 + "║",
|
|
190
|
+
"╠" + "═" * 77 + "╣",
|
|
191
|
+
"║" + " " * 77 + "║",
|
|
192
|
+
f"║ REASON: {context.reason.value:<65}║",
|
|
193
|
+
"║" + " " * 77 + "║",
|
|
194
|
+
"║ CONTEXT:" + " " * 67 + "║",
|
|
195
|
+
]
|
|
196
|
+
|
|
197
|
+
# Program info
|
|
198
|
+
lines.append(f"║ Program \"{context.program_name}\" (depth: {context.depth})" + " " * (48 - len(context.program_name)) + "║")
|
|
199
|
+
lines.append(f"║ Energy: {context.energy_remaining}/{context.energy_allocated}" + " " * 55 + "║")
|
|
200
|
+
|
|
201
|
+
# Pending actions
|
|
202
|
+
if context.pending_actions:
|
|
203
|
+
lines.append("║" + " " * 77 + "║")
|
|
204
|
+
lines.append("║ Pending actions:" + " " * 59 + "║")
|
|
205
|
+
for action in context.pending_actions[:5]: # Limit display
|
|
206
|
+
lines.append(f"║ • {action:<71}║")
|
|
207
|
+
|
|
208
|
+
# Details
|
|
209
|
+
if context.details:
|
|
210
|
+
lines.append("║" + " " * 77 + "║")
|
|
211
|
+
for key, value in list(context.details.items())[:5]:
|
|
212
|
+
lines.append(f"║ {key}: {str(value):<69}║")
|
|
213
|
+
|
|
214
|
+
# Options
|
|
215
|
+
lines.append("║" + " " * 77 + "║")
|
|
216
|
+
lines.append("║ ┌" + "─" * 73 + "┐ ║")
|
|
217
|
+
for opt in options:
|
|
218
|
+
lines.append(f"║ │ [{opt.key}] {opt.label} - {opt.description:<55}│ ║")
|
|
219
|
+
lines.append("║ └" + "─" * 73 + "┘ ║")
|
|
220
|
+
lines.append("║" + " " * 77 + "║")
|
|
221
|
+
lines.append("╚" + "═" * 77 + "╝")
|
|
222
|
+
|
|
223
|
+
return "\n".join(lines)
|
|
224
|
+
|
|
225
|
+
def get_history(self) -> list[dict]:
|
|
226
|
+
"""Get checkpoint history."""
|
|
227
|
+
return [
|
|
228
|
+
{
|
|
229
|
+
"context": {
|
|
230
|
+
"reason": ctx.reason.value,
|
|
231
|
+
"program_id": ctx.program_id,
|
|
232
|
+
"program_name": ctx.program_name,
|
|
233
|
+
"depth": ctx.depth,
|
|
234
|
+
},
|
|
235
|
+
"result": {
|
|
236
|
+
"decision": res.decision.value,
|
|
237
|
+
"timestamp": res.timestamp.isoformat(),
|
|
238
|
+
"user_input": res.user_input,
|
|
239
|
+
},
|
|
240
|
+
}
|
|
241
|
+
for ctx, res in self.checkpoints
|
|
242
|
+
]
|
package/core/program.py
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Program - Autonomous executor on The Grid.
|
|
3
|
+
|
|
4
|
+
"Programs are living entities on The Grid. They execute, they adapt, they spawn."
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from typing import Optional, Callable, Any
|
|
10
|
+
from enum import Enum
|
|
11
|
+
import uuid
|
|
12
|
+
|
|
13
|
+
from .disc import IdentityDisc
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ProgramStatus(Enum):
|
|
17
|
+
"""Status of a Program."""
|
|
18
|
+
INITIALIZING = "initializing"
|
|
19
|
+
RUNNING = "running"
|
|
20
|
+
SPAWNING = "spawning"
|
|
21
|
+
WAITING = "waiting"
|
|
22
|
+
COMPLETED = "completed"
|
|
23
|
+
FAILED = "failed"
|
|
24
|
+
DEREZZED = "derezzed"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ProgramType(Enum):
|
|
28
|
+
"""Type of Program."""
|
|
29
|
+
STANDARD = "standard"
|
|
30
|
+
RECOGNIZER = "recognizer"
|
|
31
|
+
LIGHT_CYCLE = "light_cycle" # Fast execution path
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class SpawnRequest:
|
|
36
|
+
"""Request to spawn a child Program."""
|
|
37
|
+
name: str
|
|
38
|
+
purpose: str
|
|
39
|
+
energy_budget: int
|
|
40
|
+
constraints: list[str] = field(default_factory=list)
|
|
41
|
+
approved: bool = False
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Program:
|
|
45
|
+
"""
|
|
46
|
+
Program - Autonomous executor (subagent) on The Grid.
|
|
47
|
+
|
|
48
|
+
Programs can:
|
|
49
|
+
- Execute tasks autonomously
|
|
50
|
+
- Spawn child Programs (fission)
|
|
51
|
+
- Carry context via Identity Disc
|
|
52
|
+
- Consume energy from their budget
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
name: str,
|
|
58
|
+
purpose: str,
|
|
59
|
+
parent: Optional["Program"] = None,
|
|
60
|
+
energy_budget: int = 100,
|
|
61
|
+
program_type: ProgramType = ProgramType.STANDARD,
|
|
62
|
+
):
|
|
63
|
+
self.id = str(uuid.uuid4())[:8]
|
|
64
|
+
self.name = name
|
|
65
|
+
self.purpose = purpose
|
|
66
|
+
self.program_type = program_type
|
|
67
|
+
|
|
68
|
+
self.status = ProgramStatus.INITIALIZING
|
|
69
|
+
self.created_at = datetime.now()
|
|
70
|
+
self.started_at: Optional[datetime] = None
|
|
71
|
+
self.completed_at: Optional[datetime] = None
|
|
72
|
+
|
|
73
|
+
# Fission lineage
|
|
74
|
+
self.parent = parent
|
|
75
|
+
self.children: list["Program"] = []
|
|
76
|
+
self.depth = 0 if parent is None else parent.depth + 1
|
|
77
|
+
|
|
78
|
+
# Identity Disc (context carrier)
|
|
79
|
+
parent_disc = parent.disc if parent else None
|
|
80
|
+
self.disc = IdentityDisc(
|
|
81
|
+
program_id=f"program:{self.id}",
|
|
82
|
+
purpose=purpose,
|
|
83
|
+
parent_disc=parent_disc,
|
|
84
|
+
)
|
|
85
|
+
self.disc.energy_allocated = energy_budget
|
|
86
|
+
|
|
87
|
+
# Execution state
|
|
88
|
+
self.current_action = "Initializing"
|
|
89
|
+
self.output: Any = None
|
|
90
|
+
self.error: Optional[str] = None
|
|
91
|
+
|
|
92
|
+
# Spawn requests (pending fission)
|
|
93
|
+
self.spawn_requests: list[SpawnRequest] = []
|
|
94
|
+
|
|
95
|
+
def start(self) -> None:
|
|
96
|
+
"""Start Program execution."""
|
|
97
|
+
self.status = ProgramStatus.RUNNING
|
|
98
|
+
self.started_at = datetime.now()
|
|
99
|
+
self.current_action = "Running"
|
|
100
|
+
|
|
101
|
+
def update_action(self, action: str) -> None:
|
|
102
|
+
"""Update current action."""
|
|
103
|
+
self.current_action = action
|
|
104
|
+
|
|
105
|
+
def request_spawn(
|
|
106
|
+
self,
|
|
107
|
+
name: str,
|
|
108
|
+
purpose: str,
|
|
109
|
+
energy_budget: int,
|
|
110
|
+
constraints: list[str] = None,
|
|
111
|
+
) -> SpawnRequest:
|
|
112
|
+
"""Request to spawn a child Program."""
|
|
113
|
+
request = SpawnRequest(
|
|
114
|
+
name=name,
|
|
115
|
+
purpose=purpose,
|
|
116
|
+
energy_budget=energy_budget,
|
|
117
|
+
constraints=constraints or [],
|
|
118
|
+
)
|
|
119
|
+
self.spawn_requests.append(request)
|
|
120
|
+
self.status = ProgramStatus.SPAWNING
|
|
121
|
+
return request
|
|
122
|
+
|
|
123
|
+
def spawn(self, request: SpawnRequest) -> Optional["Program"]:
|
|
124
|
+
"""Spawn a child Program from approved request."""
|
|
125
|
+
if not request.approved:
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
# Deduct energy for spawn
|
|
129
|
+
if not self.disc.consume_energy(request.energy_budget):
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
child = Program(
|
|
133
|
+
name=request.name,
|
|
134
|
+
purpose=request.purpose,
|
|
135
|
+
parent=self,
|
|
136
|
+
energy_budget=request.energy_budget,
|
|
137
|
+
)
|
|
138
|
+
for constraint in request.constraints:
|
|
139
|
+
child.disc.add_constraint(constraint)
|
|
140
|
+
|
|
141
|
+
self.children.append(child)
|
|
142
|
+
return child
|
|
143
|
+
|
|
144
|
+
def consume_energy(self, amount: int, description: str = "") -> bool:
|
|
145
|
+
"""Consume energy for an operation."""
|
|
146
|
+
success = self.disc.consume_energy(amount)
|
|
147
|
+
if success:
|
|
148
|
+
self.disc.record_discovery(
|
|
149
|
+
finding=f"Energy consumed: {amount}",
|
|
150
|
+
source="energy_system",
|
|
151
|
+
relevance="low",
|
|
152
|
+
)
|
|
153
|
+
return success
|
|
154
|
+
|
|
155
|
+
def complete(self, output: Any = None) -> None:
|
|
156
|
+
"""Mark Program as completed."""
|
|
157
|
+
self.status = ProgramStatus.COMPLETED
|
|
158
|
+
self.completed_at = datetime.now()
|
|
159
|
+
self.output = output
|
|
160
|
+
self.current_action = "Completed"
|
|
161
|
+
|
|
162
|
+
# Return unused energy to parent
|
|
163
|
+
if self.parent:
|
|
164
|
+
unused = self.disc.energy_remaining()
|
|
165
|
+
if unused > 0:
|
|
166
|
+
self.parent.disc.energy_allocated += unused
|
|
167
|
+
|
|
168
|
+
def fail(self, error: str) -> None:
|
|
169
|
+
"""Mark Program as failed."""
|
|
170
|
+
self.status = ProgramStatus.FAILED
|
|
171
|
+
self.completed_at = datetime.now()
|
|
172
|
+
self.error = error
|
|
173
|
+
self.current_action = f"Failed: {error}"
|
|
174
|
+
|
|
175
|
+
def derez(self) -> None:
|
|
176
|
+
"""Derez (terminate) this Program and all children."""
|
|
177
|
+
self.status = ProgramStatus.DEREZZED
|
|
178
|
+
self.disc.derez()
|
|
179
|
+
for child in self.children:
|
|
180
|
+
child.derez()
|
|
181
|
+
|
|
182
|
+
def wait_for_children(self) -> None:
|
|
183
|
+
"""Set status to waiting for children to complete."""
|
|
184
|
+
self.status = ProgramStatus.WAITING
|
|
185
|
+
self.current_action = "Waiting for child Programs"
|
|
186
|
+
|
|
187
|
+
def all_children_completed(self) -> bool:
|
|
188
|
+
"""Check if all child Programs are completed."""
|
|
189
|
+
return all(
|
|
190
|
+
c.status in (ProgramStatus.COMPLETED, ProgramStatus.DEREZZED)
|
|
191
|
+
for c in self.children
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
def any_child_failed(self) -> bool:
|
|
195
|
+
"""Check if any child Program failed."""
|
|
196
|
+
return any(
|
|
197
|
+
c.status == ProgramStatus.FAILED
|
|
198
|
+
for c in self.children
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
def get_lineage(self) -> list[str]:
|
|
202
|
+
"""Get the lineage chain of Program names."""
|
|
203
|
+
return self.disc.get_lineage()
|
|
204
|
+
|
|
205
|
+
def depth_display(self) -> str:
|
|
206
|
+
"""Get depth indicator for display."""
|
|
207
|
+
return " " * self.depth
|
|
208
|
+
|
|
209
|
+
def status_icon(self) -> str:
|
|
210
|
+
"""Get status icon for display."""
|
|
211
|
+
icons = {
|
|
212
|
+
ProgramStatus.INITIALIZING: "○",
|
|
213
|
+
ProgramStatus.RUNNING: "◐",
|
|
214
|
+
ProgramStatus.SPAWNING: "◎",
|
|
215
|
+
ProgramStatus.WAITING: "◑",
|
|
216
|
+
ProgramStatus.COMPLETED: "●",
|
|
217
|
+
ProgramStatus.FAILED: "✗",
|
|
218
|
+
ProgramStatus.DEREZZED: "◯",
|
|
219
|
+
}
|
|
220
|
+
return icons.get(self.status, "?")
|
|
221
|
+
|
|
222
|
+
def duration(self) -> Optional[float]:
|
|
223
|
+
"""Get execution duration in seconds."""
|
|
224
|
+
if not self.started_at:
|
|
225
|
+
return None
|
|
226
|
+
end = self.completed_at or datetime.now()
|
|
227
|
+
return (end - self.started_at).total_seconds()
|
|
228
|
+
|
|
229
|
+
def energy_percentage(self) -> float:
|
|
230
|
+
"""Get energy as percentage."""
|
|
231
|
+
return self.disc.energy_percentage()
|
|
232
|
+
|
|
233
|
+
def summary(self) -> str:
|
|
234
|
+
"""Get Program summary."""
|
|
235
|
+
lines = [
|
|
236
|
+
f"Program: {self.name} [{self.id}]",
|
|
237
|
+
f" Purpose: {self.purpose}",
|
|
238
|
+
f" Status: {self.status.value}",
|
|
239
|
+
f" Depth: {self.depth}",
|
|
240
|
+
f" Energy: {self.disc.energy_remaining()}/{self.disc.energy_allocated}",
|
|
241
|
+
f" Children: {len(self.children)}",
|
|
242
|
+
f" Action: {self.current_action}",
|
|
243
|
+
]
|
|
244
|
+
return "\n".join(lines)
|
|
245
|
+
|
|
246
|
+
def to_dict(self) -> dict:
|
|
247
|
+
"""Serialize Program to dictionary."""
|
|
248
|
+
return {
|
|
249
|
+
"id": self.id,
|
|
250
|
+
"name": self.name,
|
|
251
|
+
"purpose": self.purpose,
|
|
252
|
+
"type": self.program_type.value,
|
|
253
|
+
"status": self.status.value,
|
|
254
|
+
"depth": self.depth,
|
|
255
|
+
"current_action": self.current_action,
|
|
256
|
+
"energy_remaining": self.disc.energy_remaining(),
|
|
257
|
+
"energy_allocated": self.disc.energy_allocated,
|
|
258
|
+
"children": [c.to_dict() for c in self.children],
|
|
259
|
+
"lineage": self.get_lineage(),
|
|
260
|
+
"output": str(self.output) if self.output else None,
|
|
261
|
+
"error": self.error,
|
|
262
|
+
"created_at": self.created_at.isoformat(),
|
|
263
|
+
"started_at": self.started_at.isoformat() if self.started_at else None,
|
|
264
|
+
"completed_at": self.completed_at.isoformat() if self.completed_at else None,
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
def __repr__(self) -> str:
|
|
268
|
+
return f"Program(id={self.id}, name={self.name}, depth={self.depth})"
|